/*
 * Code for implementing top-level call control state machine.
 *
 * Multiple calls are supported.  Each call is assigned a small
 * integer index in the range 0..CFG_CALL_MAX-1.
 */

/*===========================================================================*/
/*==============  Includes  =================================================*/
/*===========================================================================*/

#include <string.h>
#include <assert.h>
#include <ctype.h>

#include <debuglog.h>
#include <spr.h>
#include <config.h>
#include <aumix.h>
#if defined(HANDSET)
#include <auplay.h>
#endif // defined(HANDSET)
#include <configFlashRecordId.h>
#include <dateTime.h>
#include <hw.h>
#include <lang.h>
#include <poller.h>
#include <configPriority.h>
#include <mainthread.h>
#if CONFIG_USE_FLASH_DB()
#include <fdb.h>
#endif
#include <lwip.h>
#include <areslib.h>
#include <nethwApi.h>

//by Mike
#include <netdb.h>
#include <uiSipRegService.h>

#if 1
#define dbgf	debugf
#endif

// Hellosoft includes; Must include <config.h> before this:
#if defined(CFG_CALL_STACK_HELLOSOFT)
#include <hs_voip_sys.h>        // from hs/net/main/src
#include <hs_voip_ccm_app_if.h>
#include <hs_voip_ccm_main.h>   // for HS_VOIP_CCM_SIP_CALLID_MASK !
#include <hs_voip_oam_cli_if.h>
#include <hs_fwk_nat_stun_if.h>
#include <hs_fwk_nat_stun_msg.h>
#include <callHsHelper.h>
#include <callHsEvent.h>
#endif //  defined(CFG_CALL_STACK_HELLOSOFT)

#include <callCtrl.h>
#include <callCtrlEventLog.h>
#include <zcomStateDispatch.h>
#include <auplay.h>
//wqn.add
#include <IMManager.h>

/*===========================================================================*/
/*==============  Data Definitions/Structures ===============================*/
/*===========================================================================*/


#define CALL_BRINGUP_RETRY_SECONDS  5    // how often to retry activating stack

//for MVQ mode
struct callCtrlVoipConfig mvqCfg={};

// Our state structure:
struct callCtrlState {
    struct callCtrlVoipConfig Flash; // Copied to/from flash
    //struct callCtrlAnswerConfig AnswerConfig;   // working copy
    // UGH, Config and Current seem to be redundant now
    struct callCtrlVoipConfig Config;   // parameters in flash or to write to
    struct callCtrlVoipConfig Current;  // parameters actually in use
    int IsInit;                 // nonzero if callCtrlInit has been called
    int VoipStackIsInit;        // nonzero if one time init done on voip stack
    int IsUp;                   // nonzero if we are ready for use
    unsigned audioOn;           /* whether the audio path is on */
    void (*AudioChangeCB)(int AudioOn);  // optional callback on audio state
    CallCtrlCallInfo *audioCall;     /* NULL or which call is using audio */
    int IncomingSequenceNumber;      /* 0 or Negative seq. of last incoming call */
    int OutgoingSequenceNumber;      /* 0 or positive seq. of last incoming call */
    int nOutgoingCalls;              /* no. of current outgoing calls */
    int nIncomingCalls;              /* no. of current incoming calls */
    /* conferenceCount NOTE: there is some feable attempt in this code
     * to support arbitrary number of calls with some of them in
     * arbitrary conferences.
     * This is however currently broken; we rely on there being a max
     * of 2 calls.
     * Currently conferenceCount has either value 2 or 0...
     * and only one conference is allowed.
     * Changing this would require careful review of the code.
     */
    int conferenceCount;   /* no. of calls in the single allowed conference */
    struct pollerTask BringUpTask; // to repeatedly retry until ready
    int Deregistering;
    avmerrCode regFailureReason;         // 0 or failure reason
    struct pollerTask ReSubscribeMwiTask;   // periodically re-subscribe for message waiting indicator
    int StartSubscribe;

    /* Current user identity "domain" portion, with port separated out */
    char SipDomainName[CFG_NETWORK_SERVER_MAX];        // domain name, no port
    unsigned SipDomainPort;

    /*
     * Currently resolved addresses.
     * Domain names have been translated into numbers; port numbers
     * separated out or defaulted.
     * ("Domain" strings just have port number stripped
    */
    unsigned SipServerAddress;
    char DottedSipServerAddressString[16];          // dotted decimal version

    unsigned ProxyServerAddress;
    char DottedProxyServerAddressString[16];        // dotted decimal version

    unsigned VoiceMailServerAddress;
    char DottedVoiceMailServerAddressString[16];    // dotted decimal version

    unsigned StunServerAddress;
    char DottedStunServerAddressString[16];        // dotted decimal version

    unsigned OutboundProxyServerAddress;
    char DottedOutboundProxyServerAddressString[16];        // dotted decimal version

    /*
     * UI update callback
     */
    void (*uiUpdateCB)(void);
    struct pollerTask UiNotifyTask;     /* notify ui in different context */

    /*
     * pending call is for call not yet assigned a stackCallId by the
     * protocol stack... or NULL if none
     */
    CallCtrlCallInfo *pendingCall;
    /*
     * pending transfer...
     */
    CallCtrlCallInfo *pendingTransfer;
    int attendedTransfer;

    /* per-call state */
    CallCtrlCallInfo  CallInfo[CFG_CALL_MAX];

    /* tone generators */
    #if defined(HANDSET)
    struct auplaySource CallerIsBeingRecordedTone;
    #endif // defined(HANDSET)

    AresJob sipProxyResolver;
    AresJob sipVoiceMailServerResolver;
    AresJob sipRegistrarResolver;
    AresJob stunServerResolver;
    AresJob OutboundProxyServerResolver;
    unsigned LastConfigTimeSec;    /* avoid too many configs */

    //for peer to peer
    //added by Mike
    bool useP2P;
    char hostIP[CFG_NETWORK_SERVER_MAX]; //used for new call
    unsigned hostPort;
    unsigned localPort; //especially for P2P local port

    #if CONFIG_USE_FLASH_DB()
    /* Flash database stuff */
    struct fdbWriteTask ConfigWriteRetry;       // allows flash write retries
    //struct callCtrlAnswerConfig AnswerFlash;    // copied to/from flash
    //struct fdbWriteTask AnswerWriteRetry;       // allows flash write retries
    struct callCtrlVoipConfig LastConfig;   // parameters in flash
    #endif
    bool Registered;

	//
	// Register retry count after register fail
	//
	int RegisterRetryCount;

  	int OwnerShown; //for caller-id on/off

	//for NATs
	//by Mike
	unsigned mapIP;
	unsigned mapPort;

   //for MVQ mode
   int bMVQMode;

    // Tianjun, 2007-8-26 17:25:40
    //  For UI Call Settings
    //
    int  bCallerIDOff;
    struct callCtrlAnswerConfig AnswerConfig;

    struct {
        int NNewMsgs;
        int NOldMsgs;
        int NNewUrgentMsgs;
        int NOldUrgentMsgs;
    } VoiceMailStatus;

	//for Register Notify...
	//by Mike
	int curRegNotifyState;
	unsigned expireSeconds;
} CallS = {};

#if defined(CFG_CALL_STACK_HELLOSOFT)
#define CALL_STACK_NULL_ID (~0)         /* standard invalid id */
#else
#error Unimplemented
#endif

/*===========================================================================*/
/*============== Voip Register Task ==========================================*/
/*===========================================================================*/
#define callCtrl_VoipRegister_Delay_Sec         2
#define callCtrl_VoipRegister_Delay_Times_Max   5
static bool callCtrl_VoipRegister_Delay_Times = 0;

static struct
{
    int isInit;
    struct pollerTask Task;
} callCtrlVoipRegister;

static void callCtrlVoipRegisterTaskHandler(struct pollerTask *taskP)
{
    if (lwipNetworkIsUp())
    {
        callCtrl_VoipRegister_Delay_Times = callCtrl_VoipRegister_Delay_Times_Max;
        callCtrlVoipConfigSet(NULL);
    }
}

static void callCtrlVoipRegisterTaskInitialize()
{
    if (callCtrlVoipRegister.isInit == 0)
    {
        callCtrlVoipRegister.isInit = 1;

        mainthreadTaskInit(
            &callCtrlVoipRegister.Task,
            "VoipRegisterTask",
            callCtrlVoipRegisterTaskHandler
            );

        pollerTaskPrioritySet(
            &callCtrlVoipRegister.Task,
            CONFIG_PRIORITY_DEFAULT
            );
    }
}

void callCtrlVoipRegisterTaskStart()
{
    callCtrlVoipRegisterTaskInitialize();

    pollerTaskLongTimeoutRelSeconds(
        &callCtrlVoipRegister.Task,
        callCtrl_VoipRegister_Delay_Sec
        );
}

/*===========================================================================*/
/*==============  Code      =================================================*/
/*===========================================================================*/
// Tianjun, 2007-8-27 8:39:50
void callCtrlSetCallerIDConfig(int bCallerIDOff)
{
    CallS.bCallerIDOff = bCallerIDOff;
} //  For UI Call Settings

void callCtrlSetAnswerConfig(const struct callCtrlAnswerConfig *AnswerConfigP)
{
    CallS.AnswerConfig = *AnswerConfigP;
} //  For UI Call Settings

int zcom_callCtrlHaveIncomingOrOutgoingCall()
{
    return ((CallS.nOutgoingCalls!=0) || (CallS.nIncomingCalls!=0));
}

// callCtrlUiNotifyTask  -- called from poller when woken due to
//      (possible) change that ui should know about.
void callCtrlUiNotifyTask( struct pollerTask *TaskP )
{
    if ( CallS.uiUpdateCB) (*CallS.uiUpdateCB)();
}

// callCtrlUpdate -- called internally when callback should be made
// to UI to update.
// This merely SCHEDULES an user interface update, therefore it can
// be called at the beginning of a function before that function has
// made all it's changes.
static void callCtrlUpdate(void)
{
    pollerTaskReady(&CallS.UiNotifyTask);
}

#if CONFIG_USE_FLASH_DB()
static void callCtrlConfigFlashWrite(void)
{       // input: CallS.Config
    // Copy fields from Config...
    debugf("callCtrlConfigFlashWrite ENTER");

    CallS.Flash = CallS.Config;

    // write with retries...
    // CAUTION!  Data not actually written to flash queue until
    //          fdbRecordWriteRetryActive(CallS.ConfigWriteRetry)
    //          returns zero.
    //          However, we can generally proceed since CallS.Flash
    //          won't be modified EXCEPT by an atomic ConfigSet call
    //          in which case the new data will get queued instead of
    //          what we previously wanted to write, which should be ok.
    fdbRecordWriteRetry( &CallS.ConfigWriteRetry,
            configFlashRecordId_VoipSetup, &CallS.Flash,
            sizeof(CallS.Flash));

    CallS.LastConfig = CallS.Config;
}
#endif


#if CONFIG_USE_FLASH_DB()
static void callCtrlAnswerConfigFlashWrite(void)
{       // input: CallS.AnswerConfig
    debugf("callCtrlAnswerConfigFlashWrite ENTER");
    CallS.Config.AnswerConfig.Version = CALLCTRL_AUTOANSWER_CONFIG_VERSION;
    if ( !memcmp( &CallS.Flash.AnswerConfig, &CallS.Config.AnswerConfig,
            sizeof(CallS.Flash.AnswerConfig)) ) {
        /* Optimize if no change */
        return;
    }
    memcpy( &CallS.Flash.AnswerConfig, &CallS.Config.AnswerConfig, sizeof(CallS.Flash.AnswerConfig));
    fdbRecordWriteRetry( &CallS.ConfigWriteRetry,
            configFlashRecordId_AutoAnswer, &CallS.Flash.AnswerConfig,
            sizeof(CallS.Flash.AnswerConfig));
}
#endif

/* callCtrlAnswerConfigGet -- return address of auto-answer
 * configuration parameters.
 * Changes can be made through the pointer but must be quickly flushed
 * by calling callCtrlAnswerConfigFlush().
 */
struct callCtrlAnswerConfig *callCtrlAnswerConfigGet(void)
{
    return &CallS.Config.AnswerConfig;
}

/* callCtrlAnswerConfigFlush -- cause changes to AnswerConfig to
 * be propogated.
 */
void callCtrlAnswerConfigFlush(void)
{
    #if CONFIG_USE_FLASH_DB()
    callCtrlAnswerConfigFlashWrite();
    #endif
    //It may cause trouble, only call when sip changed with pConfig!=NULL
    //Masked by Mike
 ///   callCtrlVoipConfigSet(NULL);        /* force reconfig */
}

void callCtrl_ResetFactory(void)
{
	memset(&CallS.Flash, 0, sizeof(CallS.Flash));

	fdbRecordWriteRetry( &CallS.ConfigWriteRetry,
		configFlashRecordId_VoipSetup, &CallS.Flash,
		sizeof(CallS.Flash));
}


// callCtrlSipServerGet -- get ip address (host order) of sip server (or zero).
//      This is for ui/diagnostic purposes...
unsigned callCtrlSipServerGet(void)
{
    return CallS.SipServerAddress;
}

// callCtrlProxyServerGet -- get ip address (host order) of proxy server (or zero).
//      This is for ui/diagnostic purposes...
unsigned callCtrlProxyServerGet(void)
{
    return CallS.ProxyServerAddress;
}

// callCtrlVoiceMailServerGet -- get ip address (host order) of proxy server (or zero).
//      This is for ui/diagnostic purposes...
unsigned callCtrlVoiceMailServerGet(void)
{
    return CallS.VoiceMailServerAddress;
}

// callCtrlStunServerGet -- get ip address (host order) of STUN server (or zero).
//      This is for ui/diagnostic purposes...
unsigned callCtrlStunServerGet(void)
{
    return CallS.StunServerAddress;
}

// callCtrlOutboundProxyServerGet -- get ip address (host order) of OutboundProxy server (or zero).
//      This is for ui/diagnostic purposes...
unsigned callCtrlOutboundProxyServerGet(void)
{
    return CallS.OutboundProxyServerAddress;
}

// callCtrlVoipConfigGet -- get copy of current VoIP configuration.
void callCtrlVoipConfigGet(
        struct callCtrlVoipConfig *ConfigP      // output
        )
{
    *ConfigP = CallS.Config;
}

// Tianjun, 2007-6-4 14:00
// callCtrlVoipConfigSetDefault -- Set default values
//      Set the fields of callCtrlVoipConfig to a dafault value.
//
void callCtrlVoipConfigSetDefault(
        struct callCtrlVoipConfig *ConfigP      // output
        )
{
    memset(ConfigP, 0, sizeof(*ConfigP));

    ConfigP->isCurrent = 0;

    ConfigP->Expire = CFG_SIP_REREGISTER_SECONDS;

    ConfigP->Version = CALL_VOIPCONFIG_VERSION;

    sprcpy( ConfigP->OurPhoneNumber,
            sizeof(ConfigP->OurPhoneNumber),
            CFG_VOIP_PHONENUMBER_DEFAULT);

    sprcpy( ConfigP->DisplayName,
            sizeof(ConfigP->DisplayName),
            langGet(LANG_Menu_VoipSetup_VOIP_Display_Name_Default)
            );

    sprcpy( ConfigP->SipDomainNameString,
        sizeof(ConfigP->SipDomainNameString),
        CFG_VOIP_SIP_DOMAIN_NAME_DEFAULT);
    ConfigP->SipDomainPort = CFG_VOIP_SIP_DOMAIN_NAME_PORT_DEFAULT;

    /* UGH If the default was non empty, would get in the way
     * of the user using domain name as default...
     */
    sprcpy( ConfigP->SipServerAddressString,
            sizeof(ConfigP->SipServerAddressString),
            CFG_VOIP_SIP_SERVER_DEFAULT);
    ConfigP->SipServerPort = CFG_VOIP_SIP_SERVER_PORT_DEFAULT;

    /* UGH If the default was non empty, would get in the way
     * of the user using domain name as default...
     */
    sprcpy( ConfigP->ProxyServerAddressString,
            sizeof(ConfigP->ProxyServerAddressString),
            CFG_VOIP_PROXY_SERVER_DEFAULT);
    ConfigP->ProxyServerPort = CFG_VOIP_PROXY_PORT_DEFAULT;

    sprcpy( ConfigP->AuthenticationUserName,
            sizeof(ConfigP->AuthenticationUserName),
            CFG_VOIP_PROXY_USER_NAME_DEFAULT);
    sprcpy( ConfigP->AuthenticationPassword,
            sizeof(ConfigP->AuthenticationPassword),
            CFG_VOIP_PROXY_PASSWORD_DEFAULT);

    sprcpy( ConfigP->VoiceMailServerAddressString,
        sizeof(ConfigP->VoiceMailServerAddressString),
        CFG_VOIP_VOICE_MAIL_SERVER_DEFAULT);
    ConfigP->VoiceMailServerPort = CFG_VOIP_VOICE_MAIL_PORT_DEFAULT;

    sprcpy(ConfigP->VoiceMailUserName,
        sizeof(ConfigP->VoiceMailUserName),
        CFG_VOIP_VOICE_MAIL_USER_NAME_DEFAULT );
    sprcpy(ConfigP->VoiceMailPassword,
        sizeof(ConfigP->VoiceMailPassword),
        CFG_VOIP_VOICE_MAIL_PASSWORD_DEFAULT );

    sprcpy( ConfigP->StunServerAddressString,
        sizeof(ConfigP->StunServerAddressString),
        CFG_VOIP_STUN_SERVER_DEFAULT);
    ConfigP->StunServerPort = CFG_VOIP_STUN_PORT_DEFAULT;

    sprcpy( ConfigP->OutboundProxyServerAddressString,
        sizeof(ConfigP->OutboundProxyServerAddressString),
        CFG_VOIP_OUTBOUND_PROXY_SERVER_DEFAULT);
    ConfigP->OutboundProxyServerServerPort = CFG_VOIP_OUTBOUND_PROXY_SERVER_PORT_DEFAULT;

    ConfigP->NATKeepAliveTime = CFG_VOIP_NAT_KEEP_ALIVE_TIME_SECONDS;

    ConfigP->EnableHighCompression = CFG_VOIP_ENABLE_HIGH_COMPRESSION_DEFAULT;

    //added by Mike
    ConfigP->nCurCodecs = 3;
    ConfigP->vCodecs[0] = TYPE_CODEC_G711;
    ConfigP->vCodecs[1] = TYPE_CODEC_G726;
    ConfigP->vCodecs[2] = TYPE_CODEC_G729;
}

// callCtrlVoipConfigDefaultGet -- get default values
//      only for those fields which clearly have a wrong value.
//      Clear *ConfigP before calling to get all defaults.
void callCtrlVoipConfigDefaultGet(
    struct callCtrlVoipConfig *ConfigP      // output
    )
{
    if (ConfigP->DisplayName[0] == '\0')
    {
        sprcpy( ConfigP->DisplayName,
                sizeof(ConfigP->DisplayName),
                langGet(LANG_Menu_VoipSetup_VOIP_Display_Name_Default)
                );
    }

    if (ConfigP->OurPhoneNumber[0] == '\0')
    {
        sprcpy( ConfigP->OurPhoneNumber,
                sizeof(ConfigP->OurPhoneNumber),
                CFG_VOIP_PHONENUMBER_DEFAULT
                );
    }

    if (ConfigP->SipServerAddressString[0] == '\0')
    {
        sprcpy( ConfigP->SipServerAddressString,
                sizeof(ConfigP->SipServerAddressString),
                CFG_VOIP_SIP_SERVER_DEFAULT
                );
    }
    if (ConfigP->SipServerPort == 0)
    {
        ConfigP->SipServerPort = CFG_VOIP_SIP_SERVER_PORT_DEFAULT;
    }

    if (ConfigP->ProxyServerAddressString[0] == '\0')
    {
        sprcpy( ConfigP->ProxyServerAddressString,
                sizeof(ConfigP->ProxyServerAddressString),
                CFG_VOIP_PROXY_SERVER_DEFAULT
                );
    }
    if (ConfigP->ProxyServerPort == 0)
    {
        ConfigP->ProxyServerPort = CFG_VOIP_PROXY_PORT_DEFAULT;
    }
    if (ConfigP->AuthenticationUserName[0] == '\0')
    {
        sprcpy( ConfigP->AuthenticationUserName,
                sizeof(ConfigP->AuthenticationUserName),
                CFG_VOIP_PROXY_USER_NAME_DEFAULT
                );
    }
    if (ConfigP->AuthenticationPassword[0] == '\0')
    {
        sprcpy( ConfigP->AuthenticationPassword,
                sizeof(ConfigP->AuthenticationPassword),
                CFG_VOIP_PROXY_PASSWORD_DEFAULT
                );
    }
    if (ConfigP->StunServerAddressString[0] == '\0')
    {
        sprcpy( ConfigP->StunServerAddressString,
                sizeof(ConfigP->StunServerAddressString),
                CFG_VOIP_STUN_SERVER_DEFAULT);
    }
    if (ConfigP->StunServerPort == 0)
    {
        ConfigP->StunServerPort = CFG_VOIP_STUN_PORT_DEFAULT;
    }
    if (ConfigP->OutboundProxyServerAddressString[0] == '\0')
    {
    	sprcpy( ConfigP->OutboundProxyServerAddressString,
                sizeof(ConfigP->OutboundProxyServerAddressString),
                CFG_VOIP_OUTBOUND_PROXY_SERVER_DEFAULT);
    }
    if (ConfigP->OutboundProxyServerServerPort == 0)
    {
    	ConfigP->OutboundProxyServerServerPort = CFG_VOIP_OUTBOUND_PROXY_SERVER_PORT_DEFAULT;
    }
    if ( ! ConfigP->VoiceMailServerAddressString[0] )
    {
            sprcpy( ConfigP->VoiceMailServerAddressString,
                sizeof(ConfigP->VoiceMailServerAddressString),
                CFG_VOIP_VOICE_MAIL_SERVER_DEFAULT);
    }
    if (ConfigP->VoiceMailServerPort == 0)
    {
	ConfigP->VoiceMailServerPort = CFG_VOIP_VOICE_MAIL_PORT_DEFAULT;
    }
    if ( ! ConfigP->VoiceMailUserName[0] )
    {
	sprcpy(ConfigP->VoiceMailUserName,
                sizeof(ConfigP->VoiceMailUserName),
                CFG_VOIP_VOICE_MAIL_USER_NAME_DEFAULT );
    }
    if ( ! ConfigP->VoiceMailPassword[0] )
    {
	sprcpy(ConfigP->VoiceMailPassword,
                sizeof(ConfigP->VoiceMailPassword),
                CFG_VOIP_VOICE_MAIL_PASSWORD_DEFAULT );
    }

    //added by Mike
    if(ConfigP->nCurCodecs<=0 || ConfigP->nCurCodecs>MAX_ZCOM_CODEC_NUM)
    {
	debugf("[Cc] Invalid codecs, use default instead!\n");
	ConfigP->nCurCodecs = 3;
	ConfigP->vCodecs[0] = TYPE_CODEC_G711;
	ConfigP->vCodecs[1] = TYPE_CODEC_G726;
	ConfigP->vCodecs[2] = TYPE_CODEC_G729;
    }
}


// callCtrlBringUpTask is callback from poller to try again after a delay
//      to voip to an "up" condition.
static void callCtrlBringUpTask(struct pollerTask *TaskP)
{
    callCtrlVoipConfigSet(NULL);        // share code
}


/* callCtrlAudioOff -- deactivate voice path/source if in use.
 */
static void callCtrlAudioOff(void)
{
    if ( CallS.audioOn ) {
        /*
         * Turn off the audio path
         */
        CallS.audioOn      = 0;
        CallS.audioCall = NULL;
        #if defined(CFG_CALL_STACK_HELLOSOFT)
        callHsHelperAudioOff();
        #endif
        if ( CallS.AudioChangeCB ) CallS.AudioChangeCB(CallS.audioOn);
        callCtrlUpdate();   /* may need UI update */
    }
}

/* callCtrlAudioOn -- activate voice path and voice source for this call.
 *      Be sure to pass the the active call.
 *      NOTE that callCtrl does NOT handle any ringing etc.
 */
static void callCtrlAudioOn(CallCtrlCallInfo *call)
{
    if ( ! CallS.audioOn )
    {
        CallS.audioOn      = 1;
        CallS.audioCall = call;
        #if defined(CFG_CALL_STACK_HELLOSOFT)
        callHsHelperAudioOn();
        #endif
        aumixCallOutMuteSet(call->muteOn);
        if ( CallS.AudioChangeCB ) CallS.AudioChangeCB(CallS.audioOn);
        callCtrlUpdate();   /* may need UI update */
    }
}


// callCtrlBringDown -- take voip stack down.
// We assume we can take it down syncronously.
void callCtrlBringDown(void)
{
    if ( CallS.VoipStackIsInit ) {
        #if defined(CFG_CALL_STACK_HELLOSOFT)
        hs_voip_sys_stop_internal();
        #else
        #error No code written for alternate voip stack.
        #endif  // CFG_CALL_STACK_HELLOSOFT
    }
    callCtrlAudioOff();
    pollerTaskStop(&CallS.ReSubscribeMwiTask);

    CallS.IsUp = 0;
    callCtrlUpdate();   /* may need UI update */
}

static void callCtrlRecomputeCurrentHelper(
        char *addressStr,       /* output: dotted decimal or empty */
        size_t strLen,
        unsigned Addr
        )
{
    if ( Addr )
    {
        spr( addressStr, strLen, "%u.%u.%u.%u",
            (Addr>>24)&0xFF, (Addr>>16)&0xFF, (Addr>>8)&0xFF, (Addr)&0xFF );
    }
    else
    {
        addressStr[0] = 0;
    }
}


static int callCtrlResolverHelper(
        const char *name,
        AresJob *job,
        const char *hostname,
        unsigned *addr,
        char *dotted            /* eventual output: dotted decimal form */
        )
{
    if (!hostname || ! *hostname) return 1;

    int status = aresJobGetStatus(job);
    switch ( status )
    {
        case ARESJOB_SUCCESS:
            debugf("[callCtrlResolverHelper] ARESJOB_SUCCESS\n");
        break;
        case ARESJOB_TRYING:
            debugf("[callCtrlResolverHelper] ARESJOB_TRYING\n");
        return 1;
        default: /* ARESJOB_STOPPED, ARESJOB_TIMEOUT: ARESJOB_SERVER_FAIL, ARESJOB_LOCAL_ERR  */
            debugf("Waiting for resolve %s %s\n", name, hostname);
            aresJobStart(job, hostname);
            status = aresJobGetStatus(job);
        break;
    }
    if ( status == ARESJOB_SUCCESS ) {
        *addr = ntohl(aresJobGetIpAddr(job).s_addr);
        callCtrlRecomputeCurrentHelper( dotted, 16, *addr );
        /* Ugh! we're printing this more than necessary !*/
        debugf("Got 0x%X ip:%s\n", *addr, dotted );
        callCtrlUpdate();   /* may need UI update */
        return 0;
    }

    debugf("[callCtrlResolverHelper] exit\n");
    return 1;
}

/* Subscribe to message waiting indication service */
void callCtrlSipSubscribeMwi(void)
{
    if ( CallS.IsUp )
    {
        #if defined(CFG_CALL_STACK_HELLOSOFT)
        int hsError;
        debugf("callCtrlSipSubscribeMwi: Begin");
        hs_voip_identity_t  caller = {};

        sprcpy( caller.uri, sizeof(caller.uri),
            CallS.Current.OurPhoneNumber);
        sprcpy( caller.display_name, sizeof(caller.display_name),
            CallS.Current.OurPhoneNumber);

        hsError = hs_voip_subscribe_mwi(&caller, NULL, NULL, 3600);
        switch(hsError)
        {
            case HS_VOIP_INVALID_CALLER:
            {
                debugf("Error:Invalid Caller\n");
            }
            break;
            case HS_VOIP_NO_MWI_ACCNT_IN_CONFIG:
            {
                debugf("Error: MWI account not in configuration for caller :%s\n", caller.uri);
            }
            break;
            case HS_SUCCESS:
            {
                debugf("The command is success \n");
            }
            break;
            default:
            {
	        debugf("Invalid returnvalue for MWI\n");
                break;
            }
        }
        #else
        #error No protocol stack defined (CFG_CALL_STACK_....)
        #endif
        return;
    }
}

void callCtrlVoipConfigSetNULL()
{

}

// callCtrlVoipConfigSet -- set new configuration.
//      This causes the voip interface to be taken down
//      while the new configuration is being put in place.
//      This may be called in two ways:
//              ConfigP == NULL: the voip stack is simply cycled
//              ConfigP != NULL: a new set of parameters is installed
//                      in flash and locally; voip stack is cycled.
void callCtrlVoipConfigSet(
        const struct callCtrlVoipConfig *ConfigP
        )
{
	debugf("In callCtrlVoipConfigSet: %p\n", ConfigP);
    callCtrlUpdate();   /* may need UI update */
    unsigned SecondsNow = mainthreadSecondsSinceBoot();

    pollerTaskStop(&CallS.BringUpTask);

	if(CallS.bMVQMode)
    {
       //use P2P mode
       //by Mike
       CallS.useP2P = 1;
    }
    else
    {
       //reset, by Mike
       CallS.useP2P = 0;
    }

    if ( ConfigP ) {
        /* Here we are being called with a CHANGE to config */
        debugf("callCtrlVoipConfigSet ENTER ConfigP==%p", ConfigP);
    } else {
        /* Don't retry more often than once a second or two
         *      ... fix for problem of infinite loop
         */
        int ElapsedSeconds = SecondsNow - CallS.LastConfigTimeSec;
        if ( ElapsedSeconds >= 0 && ElapsedSeconds < 2 ) {
            goto TryLater;
        }
    }
    CallS.LastConfigTimeSec = SecondsNow;
    // Take hellosoft stack down to avoid problems
    if ( CallS.IsUp ) {
        debugf("callCtrlVoipConfigSet: stack down for (re)configuration.");
        callCtrlBringDown();
        // CallS.IsUp will be zero now
    }

    if ( ConfigP ) {
        // Here first time after a config change (or first time) */
        CallS.Config = *ConfigP;
        callCtrlVoipConfigDefaultGet(&CallS.Config);

        CallS.SipDomainName[0] = 0;
        CallS.SipDomainPort = 0;

        /* Cancel and invalidate address resolutions.
         * We'll recompute below or later.
         */
        aresJobStop(&CallS.sipRegistrarResolver);
        CallS.SipServerAddress = 0;
        CallS.DottedSipServerAddressString[0] = 0;

        aresJobStop(&CallS.sipProxyResolver);
        CallS.ProxyServerAddress = 0;
        CallS.DottedProxyServerAddressString[0] = 0;

        aresJobStop(&CallS.sipVoiceMailServerResolver);
        CallS.VoiceMailServerAddress = 0;
        CallS.DottedVoiceMailServerAddressString[0] = 0;

        aresJobStop(&CallS.stunServerResolver);
        CallS.StunServerAddress = 0;
        CallS.DottedStunServerAddressString[0] = 0;

        aresJobStop(&CallS.OutboundProxyServerResolver);
        CallS.OutboundProxyServerAddress = 0;
        CallS.DottedOutboundProxyServerAddressString[0] = 0;
    }

    // Tianjun, 2007-8-27 8:46:21
    {
        //  For UI Call Settings
        CallS.Config.bCallIDOff = CallS.bCallerIDOff;
        CallS.Config.AnswerConfig = CallS.AnswerConfig;

        if(!CallS.bMVQMode)
        {
        #if CONFIG_USE_FLASH_DB()
        // Possibly use differences of LastConfig and Config here...
        if ( memcmp(&CallS.Config, &CallS.LastConfig, sizeof(CallS.Config)) )
        {
            // copy to flash if different
            // NOTE: if the config had not gotten into the flash queue yet,
            // no problem... the newly revised parameters will get into flash
            // queue instead.
            callCtrlConfigFlashWrite();
        }
        #endif
        }
    }

	/* Current and Config are basically redundant now... sigh */
	CallS.Current = CallS.Config;

    if(!CallS.bMVQMode)
    {
    #if CONFIG_USE_FLASH_DB()
    // Don't bring "up" until it is at least in the flash queue
    // to help recover from any flash queue congestion.
    if ( fdbRecordWriteRetryActive(&CallS.ConfigWriteRetry) ) {
        debugf("callCtrl: config not written to flash yet.");
        goto TryLater;
    }
    #endif
    }

    // If network is up then we can start?
    if ( ! lwipNetworkIsUp() ) {
        //
        // zcomStateDispatch module will try later
        // after indicate IP ready
        //
        goto TryLater;
    }

    CallS.Registered = false;

	//under new reg scheme, we must wait some time
	//by Mike
	if(CallS.curRegNotifyState != _REG_NOTIFY_STATE_STOP)
	{
		CallS.curRegNotifyState = _REG_NOTIFY_STATE_STOP;
		CallS.expireSeconds = 0;
		goto TryLater;
	}

    if (CallS.Config.OurPhoneNumber[0] == '\0') {
        debugf("callCtrl: OurPhoneNumber not set yet\n");
        goto zcom_config_error;
    }

	//for P2P only
	//by Mike
	if ( CallS.Config.SipServerAddressString[0]==0 )
	{
		CallS.useP2P = true;
	}
    /* Do the same check as the user sees.
     * This is also where we (re)start name resolution and
     * pursue NAT traversal.
     */
	 //by Mike
#if 0
    if ( callCtrlCheckConfig() ) {
        debugf("Not sufficiently configured... will try later.");
        goto zcom_config_error;
    }
    if ( callCtrlCheckResolve() ) {
        debugf("Not sufficiently resolved... will try later.");
        goto TryLater;
    }
#else
	if (CallS.Config.SipServerAddressString[0] == '\0') {
        debugf("callCtrl: sip server not set yet\n");
    }
    if (CallS.Config.ProxyServerAddressString[0] == '\0') {
        debugf("callCtrl: sip proxy not set yet\n");
    }

	//by Mike
	dbgf("Set default port...\n");
	{
		unsigned port = CallS.localPort;
		if(port==0)
			port = CFG_VOIP_PROXY_PORT_DEFAULT; //assigned as default
		callCtrlSetLocalPort(port);
	}

    // Tianjun, 2007-12-12 17:47:56
	if (callCtrl_VoipRegister_Delay_Times > 0)
    {
    	int err = callCtrlCheckResolve();
        if (err)
        {
            //
            // Check Resolve failed...
            //
            callCtrl_VoipRegister_Delay_Times--;

    		goto label_voipRegisterDelay;
        }
        else
        {
            //
            // Check Resolve succeeded...
            //
            callCtrl_VoipRegister_Delay_Times = 0;
        }
    }

	//check server cfg...
	//by Mike
	int err = callCtrlCheckResolve();

    if (err == 0) {
		//resolve succ...
		zcom_IndicateState(Step_SIP,
			State_SIP_Registering,
			ErrorCode_SIP_NOERROR
			);

    }
	else
	{
		debugf("Error code: %d\n", err);
		//make things simple for sip resolve here
		//by Mike
		zcom_IndicateState(Step_SIP,
			State_SIP_Idle,
			CallS.useP2P?
			    ErrorCode_SIP_STUNSERVER_ADDRESS_RESOLVER_ERROR :
			        ErrorCode_SIP_PROXYSERVER_ADDRESS_RESOLVER_ERROR
    		);

		goto TryLater;
	}
#endif //by Mike

    if ( CallS.Current.SipDomainNameString[0] )
    {
		sprcpy(CallS.SipDomainName, sizeof(CallS.SipDomainName), CallS.Current.SipDomainNameString);
		CallS.SipDomainPort = CallS.Current.SipDomainPort;
    }
	else
	{
		sprcpy(CallS.SipDomainName, sizeof(CallS.SipDomainName), CallS.Current.SipServerAddressString);
		CallS.SipDomainPort = CallS.Current.SipServerPort;
	}

   //set call-id flag
	//by Mike
	callCtrlSetOwnerShown(!CallS.Current.bCallIDOff);

    // Set all parameters we care about, to be sure
    #if defined(CFG_CALL_STACK_HELLOSOFT)
    // Refer to hs_voip_oam_config_set.c
    // NOTE: there should be no problem setting these before calling
    // init....
    hs_voip_cm_set_ip_address(
        lwipNetworkIpAddressStringGet(),
        lwipNetworkNetmaskStringGet(),
        lwipNetworkGatewayStringGet(),
        #if 0   // this is never set and is meaningless
        lwipNetworkDomainStringGet()
        #else   // and hellsoft never actually uses this
        "yourdomainname"
        #endif
        );
    hs_voip_cm_set_endpoint_user_map( 0/*endpoint*/,
        CallS.Current.OurPhoneNumber );
    hs_voip_cm_set_sip_registrar(
        "hack", /* this should be our phone number, but i hacked it */
        CallS.DottedSipServerAddressString,
        CallS.Current.SipServerPort );
    hs_voip_cm_set_sip_proxy(
        "hack", /* this should be our phone number, but i hacked it */
        CallS.DottedProxyServerAddressString,
        CallS.Current.ProxyServerPort );
    // NO: hs_voip_cm_set_dns_server_ip(...)
    // ? hs_voip_cm_set_nat_ip(...)
    if ( CallS.Current.AuthenticationUserName[0] ) {
        hs_voip_cm_set_user_name(
            "hack", /* this should be our phone number, but i hacked it */
            CallS.Current.AuthenticationUserName
            );
        } else {
        /* Default to using phone number as user name */
        hs_voip_cm_set_user_name(
            "hack", /* this should be our phone number, but i hacked it */
            CallS.Current.OurPhoneNumber
            );
    }
    // ? hs_voip_cm_set_user_agent_name(...)
    hs_voip_cm_set_sip_password(
        "hack", /* this should be our phone number, but i hacked it */
        CallS.Current.AuthenticationPassword
        );

    /* we will take care sip registration ourselves*/
    hs_voip_cm_set_auto_sip_registration_flag(0/*endpoint*/, HS_FALSE);
    debugf("auto sip registration flags %d\n", hs_voip_cm_get_auto_sip_registration_flag(0));

    /* we need to set this in order to register correctly */
    hs_voip_cm_set_domain_name(
        CallS.SipDomainName, CallS.SipDomainPort);

    //Don't use auto-answer mode since i don't know how it works and it might be used for other aims...
    hs_voip_cm_set_ep_mvq_ready_for_call_in(0/*hs_uint16   ep_id*/, CallS.bMVQMode);

	 unsigned ProxyMode = 0;
	 /* NAT STUN, we have checked it in callCtrlCheck(), so we setup based on result */
	 switch (hs_fwk_nat_stun_get_binding_type())
    {
	 case HS_FWK_NAT_TYPE_FULLCONE:        /* Full Cone NAT */
            /* fall through */
	 case HS_FWK_NAT_TYPE_RESTRICTED:      /* Restricted Cone NAT */
            /* fall through */
	 case HS_FWK_NAT_TYPE_PORTRESTRICTED:  /* Port Restricted Cone NAT */
		if (CallS.DottedStunServerAddressString[0]!=0)
		{
			hs_voip_cm_set_nat_type(HS_VOIP_DYNAMIC_NAT);
			hs_voip_cm_set_stun_server_ip(CallS.DottedStunServerAddressString, CallS.Current.StunServerPort);
			break;
		}
		/* fall  through if there is no STUN server */
	 case HS_FWK_NAT_TYPE_ERROR:
            /* fall  through */
        case HS_FWK_NAT_TYPE_SYMMETRIC:
            /* fall  through */
	 case HS_FWK_NAT_TYPE_NO_NAT:
      //How to check or know if we are in NAT or not without STUN?
      //Now just leave user to tell by configuration...
      //by Mike
		      /* also fall  through */
	 default:
		hs_voip_cm_set_nat_type(HS_VOIP_NO_NAT);
		hs_fwk_nat_stun_stop();
		/* should we use outbound all the time??? */
		if (CallS.OutboundProxyServerAddress !=0 )
		{
			hs_voip_cm_set_outbound_proxy(
				CallS.Current.OurPhoneNumber,
				CallS.DottedOutboundProxyServerAddressString,
				CallS.Current.OutboundProxyServerServerPort
				);
			ProxyMode = 1;
		}
		break;
    }
    hs_voip_cm_set_ep_proxy_outbound_flag(0/*hs_uint16   ep_id*/, ProxyMode );
    hs_voip_cm_set_always_use_outbound_proxy_flag(ProxyMode);
    hs_voip_cm_set_proxy_outbound_flag(CallS.Current.OurPhoneNumber, ProxyMode);

    hs_voip_cm_set_nat_bind_interval(CallS.Current.NATKeepAliveTime);

    hs_voip_cm_set_registration_timeout(CallS.Current.Expire);

	int bG711=0, bG726=0;
	int pro = 0;
	for ( int ICodec = 0; ICodec < CallS.Current.nCurCodecs; ICodec++ ) {
		switch(CallS.Current.vCodecs[ICodec])
		{
		case TYPE_CODEC_ILBC:
			hs_voip_cm_set_codec(HS_VOIP_CODEC_ILBC, 1);
			hs_voip_cm_set_codec_priority(
				++pro,  // 1 puts it highest priority
				HS_VOIP_CODEC_ILBC);
			break;
		case TYPE_CODEC_G729:
			hs_voip_cm_set_codec(HS_VOIP_CODEC_G729A, 1);
			hs_voip_cm_set_codec_priority(
				++pro,  // 1 puts it highest priority
				HS_VOIP_CODEC_G729A);
			break;
		case TYPE_CODEC_G726:
			if(!bG726)
			{
				bG726 = 1;
				hs_voip_cm_set_codec(HS_VOIP_CODEC_G726_40, 1);
				hs_voip_cm_set_codec_priority(
					++pro,  // 1 puts it highest priority
					HS_VOIP_CODEC_G726_40);

				//extend other
				hs_voip_cm_set_codec(HS_VOIP_CODEC_G726_32, 1);
				hs_voip_cm_set_codec_priority(
					++pro,  // 1 puts it highest priority
					HS_VOIP_CODEC_G726_32);

				hs_voip_cm_set_codec(HS_VOIP_CODEC_G726_24, 1);
				hs_voip_cm_set_codec_priority(
					++pro,  // 1 puts it highest priority
					HS_VOIP_CODEC_G726_24);

				hs_voip_cm_set_codec(HS_VOIP_CODEC_G726_16, 1);
				hs_voip_cm_set_codec_priority(
					++pro,  // 1 puts it highest priority
					HS_VOIP_CODEC_G726_16);
			}
			break;
		case TYPE_CODEC_G711:
			if(!bG711)
			{
				bG711 = 1;
				hs_voip_cm_set_codec(HS_VOIP_CODEC_PCMA, 1);
				hs_voip_cm_set_codec_priority(
					++pro,  // 1 puts it highest priority
					HS_VOIP_CODEC_PCMA);

				//extend other
				hs_voip_cm_set_codec(HS_VOIP_CODEC_PCMU, 1);
				hs_voip_cm_set_codec_priority(
					++pro,  // 1 puts it highest priority
					HS_VOIP_CODEC_PCMU);
			}
			break;
		default:
			assert(0);
			break;
		}
	}

    if( CFG_SIP_PRIVACY_DEFAULT )
    {
        hs_voip_cm_set_ep_privacy_levels(0/*endpoint*/, CFG_SIP_PRIVACY_STRING);
    }

    hs_voip_cm_set_transfer_option(0/*endpoint*/, HS_VOIP_ALLOWED);
    hs_voip_cm_set_ep_call_forward_option(0/*endpoint*/, HS_VOIP_ALLOWED);

    /* set call forward number and answer timer */
    {
        char forward_string[HS_VOIP_SIP_URL_SIZE];
        forward_string[0] = 0;
        int TimeoutSeconds = 180;
        char *always = "";
        char *busy = "";
        char *noanswer = "";
        if ( CallS.Current.AnswerConfig.AutoAnswerMode ==
                    callCtrlAutoAnswerMode_eForward &&
                CallS.Current.AnswerConfig.Forward.PhoneNumber[0] )
       {
/*
            spr(forward_string, sizeof(forward_string), "sip:%s@%s:%d",
                CallS.Current.AnswerConfig.Forward.PhoneNumber,
                CallS.SipServerDomain,
                CallS.SipServerPort);
*/
            if( CallS.SipDomainPort )
            {
            spr(forward_string, sizeof(forward_string), "sip:%s@%s:%d",
                CallS.Current.AnswerConfig.Forward.PhoneNumber,
                    CallS.SipDomainName, CallS.SipDomainPort);
            }
            else
            {
                spr(forward_string, sizeof(forward_string), "sip:%s@%s",
                    CallS.Current.AnswerConfig.Forward.PhoneNumber,
                    CallS.SipDomainName);
            }
            TimeoutSeconds = CallS.Current.AnswerConfig.Forward.TimeoutSeconds;
            if ( ! CallS.Current.AnswerConfig.Forward.TimeoutSet )
                TimeoutSeconds = CFG_NO_ANSWER_FORWARD_SECONDS;
            switch ( CallS.Current.AnswerConfig.Forward.When ) {
                default:
                case callCtrlAutoAnswerWhen_eNever :
                     TimeoutSeconds = 180;
                break;
                case callCtrlAutoAnswerWhen_eAlways :
                    always = forward_string;
                    TimeoutSeconds = 180;
                break;
                case callCtrlAutoAnswerWhen_eIfBusy :
                    busy = forward_string;
                    TimeoutSeconds = 180;
                break;
                case callCtrlAutoAnswerWhen_eIfNoAnswer :
                    noanswer = forward_string;
                break;
                case callCtrlAutoAnswerWhen_eIfBusyOrNoAnswer :
                    busy = forward_string;
                    noanswer = forward_string;
                break;
            }
        if ( TimeoutSeconds < 1 ) TimeoutSeconds = 1; /* ugh! */
        /* Note there is also an "auto answer timeout" but this
         * actually answers (could be useful for phone machine mode)
         * instead of forwarding.
         */
        hs_voip_cm_set_answer_timeout(TimeoutSeconds);
        hs_voip_cm_set_ep_call_forward_always( 0/*endpoint*/, always);
        hs_voip_cm_set_ep_call_forward_on_busy( 0/*endpoint*/, busy);
        hs_voip_cm_set_ep_call_forward_on_no_answer( 0/*endpoint*/, noanswer);
    }
        else
        {
            hs_voip_cm_set_answer_timeout(180);
            hs_voip_cm_set_ep_call_forward_always( 0/*endpoint*/, "");
            hs_voip_cm_set_ep_call_forward_on_busy( 0/*endpoint*/, "");
            hs_voip_cm_set_ep_call_forward_on_no_answer( 0/*endpoint*/, "");
        }
    }
    #if 0       // NOT YET........ FINISH THIS
    /* Set "Reject" mode (allows changing time that phone can ring ) */
    if ( CallS.Current.AnswerConfig.AutoAnswerMode == callCtrlAutoAnswerMode_eReject )
    {
        TimeoutSeconds = CallS.Current.AnswerConfig.Reject.TimeoutSeconds;
        if ( ! CallS.Current.AnswerConfig.Reject.TimeoutSet )
            TimeoutSeconds = CFG_NO_ANSWER_REJECT_SECONDS;
        hs_voip_cm_set_answer_timeout(TimeoutSeconds);
    }
    #endif
    #if 0       // NOT YET........ FINISH THIS
    /* Set "Answer" mode (allows full audio auto-answering) */
    if ( CallS.Current.AnswerConfig.AutoAnswerMode == callCtrlAutoAnswerMode_eAnswer )
    {
        TimeoutSeconds = CallS.Current.AnswerConfig.Answer.TimeoutSeconds;
        if ( ! CallS.Current.AnswerConfig.Answer.TimeoutSet )
            TimeoutSeconds = CFG_NO_ANSWER_ANSWER_SECONDS;
        hs_voip_cm_set_answer_timeout(TimeoutSeconds);
    }
    #endif
    #if 0       // NOT YET........ FINISH THIS
    /* Set "Messaage" mode (allows auto-answering with recorded message) */
    if ( CallS.Current.AnswerConfig.AutoAnswerMode == callCtrlAutoAnswerMode_eMessage )
    {
        TimeoutSeconds = CallS.Current.AnswerConfig.Message.TimeoutSeconds;
        if ( ! CallS.Current.AnswerConfig.Message.TimeoutSet )
            TimeoutSeconds = CFG_NO_ANSWER_MESSAGE_SECONDS;
        hs_voip_cm_set_answer_timeout(TimeoutSeconds);
        /* This will require some outside support to play the message
         * etc.
         */
    }
    #endif

    /* default 15msec is very small. we need to set it according to SIP statndard*/
    hs_voip_cm_set_sip_time_interval(500);
    /* HS_VOIP_DTMF_INBAND_PASS_THROUGH, HS_VOIP_DTMF_INBAND_PCMU, HS_VOIP_DTMF_INBAND_PCMA*/
    hs_voip_cm_set_dtmf_type(HS_VOIP_DTMF_2833_RELAY);

    // ? hs_voip_cm_set_endpoint_lec(...)
    // ? hs_voip_cm_set_endpoint_tx_level(...)
    // ? hs_voip_cm_set_endpoint_rx_level(...)
    // ? hs_voip_cm_set_endpoint_txrx_delay(...)
    // ? hs_voip_cm_set_endpoint_device_buf_len(...)
    // ? hs_voip_cm_set_email_id(...)
    // ? ...... and many more (defaults may be ok) .......
    // ? hs_voip_cm_set_auto_call_transfer_flag(...) -- call transfer policy
    // ? hs_voip_cm_set_auto_call_waiting_flag(...) -- call waiting policy
    // ? hs_voip_cm_set_ep_call_forward_always(...) -- automatic forwarding
    // ? hs_voip_cm_set_ep_call_forward_on_busy(..) -- automatic forwarding
    // ? ... etc. ...

    if( CallS.DottedVoiceMailServerAddressString[0] )
    {
        hs_voip_cm_set_mwi_server(0, CallS.DottedVoiceMailServerAddressString);
        hs_voip_cm_set_mwi_auth(0, CallS.Current.VoiceMailUserName, CallS.Current.VoiceMailPassword);
        hs_voip_cm_set_mwi_refresh_timeout(0, 3600);
    }

    if ( ! CallS.VoipStackIsInit ) {
        debugf("callCtrl: calling hs_voip_sys_init_internal etc.");
        /* NOTE! See hs/oam/src/hs_voip_oam_config.c
         * for a great many parameters!
         */
        if ( hs_voip_sys_init_internal(NULL) )
        {
            debugf("callCtrlInit: FAILURE from hs_voip_sys_init_internal!\n");
            return;
        }
        debugf("callCtrl: after hs_voip_sys_init_internal");
        CallS.VoipStackIsInit = 1;
    }
    if ( hs_voip_sys_start_internal() )
    {
        debugf("callCtrl: FAILURE from hs_voip_sys_start_internal!\n");
        goto TryLater;
    }
    debugf("callCtrl: after hs_voip_sys_start_internal");

    CallS.StartSubscribe = 0;

	/* de-register first
	It can clear the expired NAT mapping account from SIP server.

	  However, de-register will disable the forking feature and user cannot login in
	  multiple place (Phone & PC) at the same time.
	*/
	hs_voip_identity_t identity = {};
	hs_voip_identity_t contact_remove = { };
	sprcpy( identity.uri, sizeof(identity.uri), CallS.Current.OurPhoneNumber);
	sprcpy( identity.display_name, sizeof(identity.display_name), CallS.Current.OurPhoneNumber);
	strcpy(contact_remove.uri, "*");

    if(!CallS.useP2P)
	{
		CallS.Deregistering = 1;
		hs_voip_sip_deregister_req(&identity, &contact_remove);
		//change state to start Reg-manager
		//by Mike
		CallS.curRegNotifyState = _REG_NOTIFY_STATE_RUNNING;
	}
	else
	{
		if(err)
		{
			zcom_IndicateState(Step_SIP,
				State_SIP_Idle,
				ErrorCode_SIP_STUNSERVER_ADDRESS_RESOLVER_ERROR
			);

		}
		else
		{
			zcom_IndicateState(Step_SIP, State_SIP_P2P_Ready, ErrorCode_SIP_NOERROR);
         //stop register try
         //by Mike
         pollerTaskStop(&CallS.ReSubscribeMwiTask);
		}
	}
#else

#error No code written for alternate voip stack.

#endif  // CFG_CALL_STACK_HELLOSOFT

	debugf("callCtrl: VOIP STACK IS READY PENDING RE-REGISTRATION");
	CallS.RegisterRetryCount = 0;
    CallS.IsUp = 1;

    if(CallS.bMVQMode==1)
    {
        //First reset A-A flag in case...
        hs_voip_cm_set_endpoint_auto_answer_flag(0, false);

        CallS.bMVQMode = 2;
        printf("\n\n*********************************************************\n");
        printf("====>Ready for receiving in-coming p2p call of MVQ test!\n");
        printf("\n*********************************************************\n\n");
    }

    return;

label_voipRegisterDelay:
    pollerTaskLongTimeoutRelSeconds(
        &CallS.BringUpTask, callCtrl_VoipRegister_Delay_Sec);
    return;

TryLater:   // Here if we can't bring it up now.
    pollerTaskLongTimeoutRelSeconds(
        &CallS.BringUpTask, CALL_BRINGUP_RETRY_SECONDS );
    return;

zcom_config_error:
	zcom_IndicateState(
	    Step_SIP,
		State_SIP_Idle,
		ErrorCode_SIP_Config_Error
		);
    return;
}

/*
 * Find a call for the given slot.
 * Returns NULL if invalid slot.
 */
CallCtrlCallInfo *callCtrlFindCallBySlot(
        int callIdx  /* 0 to CFG_CALL_MAX-1 */
        )
{
    if ( callIdx >= 0 && callIdx < CFG_CALL_MAX ) {
        CallCtrlCallInfo *call = &CallS.CallInfo[callIdx];
        return call;
    }
    return NULL;
}

/*
 * Find a call matching slot (is given) and sequence number (if given)
 */
avmerrCode callCtrlFindCallBySlotOrSequence(
        CallCtrlCallInfo **callPP,      /* output */
        int callIdx,            // which call slot (0 or 1, or -1 for use seq)
        int SequenceNumber      // 0 or must match call
        )
{
    CallCtrlCallInfo *call = NULL;      /* default, bad */
    *callPP = NULL;     /* default, bad */
    if ( callIdx >= 0 && callIdx < CFG_CALL_MAX ) {
        call = &CallS.CallInfo[callIdx];
        /* If nonzero sequence number provided, it must match */
        if ( SequenceNumber &&
             call->SequenceNumber != SequenceNumber )
                return avmerrCallCtrl_CallChangedFailed_NoCall;
        goto GotCall;
    }
    if ( SequenceNumber == 0 ) return avmerr_Call_Failed_Reason_Bad_Slot;
    for ( callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
        call = &CallS.CallInfo[callIdx];
        if ( call->SequenceNumber == SequenceNumber ) goto GotCall;
    }
    return avmerrCallCtrl_CallChangedFailed_NoCall;
    GotCall:
    *callPP = call;
    return 0;
}

/*
 * Return a call in given state, or NULL if none.
 * Call repeatedly to find all in given state.
 */
CallCtrlCallInfo *callCtrlFindCallByState(
        enum callCtrlCallState state
        )
{
    int callIdx;
    for ( callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
        CallCtrlCallInfo *call = &CallS.CallInfo[callIdx];
        if ( call->state == state ) return call;
    }
    return NULL;
}

/*
 * Compare stack ids for equivalency.
 * Returns nonzero if equivalent.
 */
static inline int callCtrlStackIdEqual(
        unsigned stackCallId1,
        unsigned stackCallId2
        )
{
    #if defined(CFG_CALL_STACK_HELLOSOFT)
    /* This is poorly documented in the hellosoft code.
     * The format of an id is:
     *          bits 31..16 are a randomly assigned number.
     *          bits 15..8 are ccm number, of which
     *                  bits 9..8 are the object number.
     *          bits 7..0 are the sip number (what is this?)
     * There is a comment that implies that either the ccm number
     *          or the sip number may be 0xff (apparently meaning
     *          "don't care") but not both.
     * Apparently for API purposes? it is really only the sip
     * number that we may ignore.
     * The sip number is not be initially assigned and is reported as FF;
     * it is assigned later...
     */
    return (stackCallId1 & ~HS_VOIP_CCM_SIP_CALLID_MASK) ==
                (stackCallId2 & ~HS_VOIP_CCM_SIP_CALLID_MASK);
    #else
    #error Unimplemented
    #endif
}

/*
 * Return a call with a matching stack id.
 * Note that for hellosoft, this is not an exact bit comparison.
 */
static CallCtrlCallInfo *callCtrlFindCallByStackId(
        unsigned stackCallId
        )
{
    for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
        CallCtrlCallInfo *call = &CallS.CallInfo[callIdx];
        if ( callCtrlStackIdEqual( call->stackCallId, stackCallId ) )
                return call;
    }
    return NULL;
}


/*
 * Start, continue or stop an alert.  This is either an incoming call
 * ring or a ring-back tone for an outbound call.
 *
 * This gets called each time a call enters/leaves the alert
 * state, and each time the audio path is enabled/disabled.
 *
 * For an inbound call alert we make the phone ring by:
 *
 *   - if the audio path is off (ie: no active calls or all calls
 *     on hold), turn on the audio speaker, play a melody and issue
 *     a vibrate if present/enabled, or
 *
 *   - if the audio path is on (ie: active call not on hold), play
 *     a short tone in the audio path so the user can hear it
 *
 *   - we might have to switch between these modes if the active
 *     call stops or goes on/off hold
 *
 * For an outbound call we deliver the current ring-back tone
 * (eg: busy, rining, etc) to the local user.
 *
 * Also, there could be multiple alerts.  We just process the earliest
 * one.  Once it is answered or rejected then we check the others and
 * restart the alert if necessary.
 *
 * TODO %%%%%%%%%%%%%%%%%%%%%%%% the above is not implemented
 * or necessarily even correct... the implementation is not based
 * upon a careful plan (sorry).
 */
void callCtrlAlertUpdate(CallCtrlCallInfo *call)
{
    /*
     * Tell the UI to update
     */
    callCtrlUpdate();
}

/*
 * Remove a call from a conference.
 * No affect if the call is not conferenced.
 * If the conference would have only one call in it, the conference
 * is terminated.
 * The calls remain as independent calls.
 */
void callCtrlDeConference(
        CallCtrlCallInfo *call          /* call to remove from conference */
    )
{
    callCtrlUpdate();   /* may need UI update */
    assert(call != NULL);
    debugf("callCtrlDeConference(%d)\n", call->idx);

    if ( call->state == CC_CALLINFO_NOTUSED ) return;
    if ( call->state == CC_CALLINFO_ZOMBIE ) return;

    #if defined(CFG_CALL_STACK_HELLOSOFT)
    /* ASSUME that if there is a conference call, all of the
     * calls are part of the conference call.
     * This works ok when we only allow 2 calls...
     */
    if ( call->conferenced ) {
        hs_voip_stop_conference(call->stackCallId);
        CallS.conferenceCount = 0;
        /* Since ASSUME max 1 conference, then all calls are no
         * longer in conference.
         */
        for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
            CallCtrlCallInfo *otherCall = &CallS.CallInfo[callIdx];
            otherCall->conferenced = 0;
        }
    }
    #endif
}

/*
 * Remove a call from a conference.
 * No affect if the call is not conferenced.
 * If the conference would have only one call in it, the conference
 * is terminated.
 * The calls remain as independent calls.
 */
avmerrCode callCtrlDeConferenceBySlot(
        int callIdx,
        int SequenceNumber      /* 0=don't care, else check for match */
    )
{
    CallCtrlCallInfo *call;
    avmerrCode err =
        callCtrlFindCallBySlotOrSequence(&call, callIdx, SequenceNumber);
    if ( err ) {
        debugf("callCtrlDeConferenceBySlot: call idx %d + seq %d invalid!",
            callIdx, SequenceNumber);
        return err;
    }
    callCtrlDeConference(call);
    return 0;
}



/* Release stack resources for a call
 */
static void callCtrlStackReleaseCall(
        unsigned stackCallId
    )
{
    #if defined(CFG_CALL_STACK_HELLOSOFT)
    // for hellosoft version 2:
    // typedef struct hs_voip_identity
    // {
    //    hs_char uri[HS_VOIP_SIP_URL_SIZE];
    //    hs_char display_name[HS_VOIP_SIP_DISPLAY_NAME_SIZE];
    // } hs_voip_identity_t;
    hs_voip_identity_t identity = {};
    sprcpy( identity.uri, sizeof(identity.uri),
        CallS.Current.OurPhoneNumber);
    sprcpy( identity.display_name, sizeof(identity.display_name),
        CallS.Current.OurPhoneNumber);
    // hs_int32
    // hs_voip_release_call
    //    (
    //       hs_voip_identity_t *caller,
    //       hs_uint32  call_id,
    //       hs_voip_release_call_reason_code_t reason_code,
    //       hs_char    reason_phrase[HS_VOIP_REASON_PHRASE_SIZE]
    //    )
    hs_voip_release_call(
        &identity,
        stackCallId,
        HS_VOIP_RELEASE_NORMAL,     // HACK! "should" give detailed reason
        ""          // This could be a message string to send via SIP
        );
    #else
    #error Unimplemented
    #endif
    return;
}


/*
 * Terminate a call, for real...
 * except that the call slot is left as ZOMBIE to force a final
 * stage of ui cleanup.
 */
static void callCtrlTerminateLow(
        CallCtrlCallInfo *call          /* call to terminate */
    )
{
    callCtrlUpdate();   /* may need UI update */
    assert(call != NULL);
    debugf("callCtrlTerminateLow(%d)\n", call->idx);

    if ( call->state == CC_CALLINFO_NOTUSED ) return;
    if ( call->state == CC_CALLINFO_ZOMBIE ) return;

    #if defined(CFG_CALL_STACK_HELLOSOFT)
    // The documentation isn't crystal clear.... apparently
    //  hs_voip_release_call will do it all and
    //  hs_voip_release_complete is obsolete...

    // for hellosoft version 2:
    // typedef struct hs_voip_identity
    // {
    //    hs_char uri[HS_VOIP_SIP_URL_SIZE];
    //    hs_char display_name[HS_VOIP_SIP_DISPLAY_NAME_SIZE];
    // } hs_voip_identity_t;

    /* ASSUME that if there is a conference call, all of the
     * calls are part of the conference call.
     * This works ok when we only allow 2 calls...
     */
    if ( call->conferenced ) {
        hs_voip_stop_conference(call->stackCallId);
        CallS.conferenceCount = 0;
        /* Since ASSUME max 1 conference, then all calls are no
         * longer in conference.
         */
        for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
            CallCtrlCallInfo *otherCall = &CallS.CallInfo[callIdx];
            otherCall->conferenced = 0;
        }
    }

    if(!call->outgoing && callCtrlIsMvqMode())
    {
        printf("====>Hang up\n");
    }
    callCtrlStackReleaseCall(call->stackCallId);

    // was: hs_voip_release_complete(call->stackCallId);
    call->stackCallId = CALL_STACK_NULL_ID;
    #else       // protocol stack
    #error No protocol stack defined (CFG_CALL_STACK_....)
    #endif

    if ( call == CallS.pendingCall )
    {
        CallS.pendingCall = NULL;
    }
    if ( call->outgoing ) CallS.nOutgoingCalls--;
    else CallS.nIncomingCalls--;

    /* See if there is another call up... if not, take down audio */
    CallCtrlCallInfo *call2 = NULL;
    for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ )
    {
        call2 = &CallS.CallInfo[callIdx];
        if ( ( call2->state != CC_CALLINFO_NOTUSED ) &&
            (call2->state != CC_CALLINFO_ZOMBIE) && (call2 != call) )
        {
            break;
        }
        call2 = NULL;
	}
	if( !call2 )
	{
		if ( CallS.audioCall == call && CallS.audioOn ) {
			debugf("callCtrlTerminateLow(%d) audio off.\n", call->idx);
			callCtrlAudioOff();
		}
	}

    call->holdOn = 0;
    if ( call->transferOn ) CallS.pendingTransfer = NULL;
    call->transferOn = 0;
    call->ringing = 0;
    call->state = CC_CALLINFO_ZOMBIE;

    //reset flag
    //by Mike
    if(CallS.Registered && CallS.useP2P)
    {
       CallS.useP2P = 0;
       callCtrlUpdate();
    }
    return;
}

/*
 * Kill a call, initiated by the local user.
 * The call goes to ZOMBIE state which should be later ack'd by ui.
 */
void callCtrlKill( CallCtrlCallInfo *call )
{
    assert(call != NULL);
    switch( call->state )
    {
        case CC_CALLINFO_NOTUSED         :
        case CC_CALLINFO_ZOMBIE          :
        return;
        default:
        break;
    }
        callCtrlTerminateLow(call);
    callCtrlUpdate();
    return;
}


/*
 * Free up the call slot... call from ui only on reaching ZOMBIE state.
 */
void callCtrlFree( CallCtrlCallInfo *call )
{
    callCtrlUpdate();   /* may need UI update */
    assert(call != NULL);
    assert(call->state == CC_CALLINFO_ZOMBIE);
    /*
    * For sanity, clear all fields but retain table index
    * (which is used for debugging...).
    */
    int callIdx = call->idx;
    memset(call, 0, sizeof(*call));
    // Repair the damage from memset:
    call->state = CC_CALLINFO_NOTUSED;
    call->idx   = callIdx;
    call->stackCallId = CALL_STACK_NULL_ID;
    if ( (CallS.nOutgoingCalls + CallS.nIncomingCalls) == 0 ) {
        /*
        * Tell wlan to optimize power
        */
        nethwVoipCallModeSet(0);
    }
    return;
}

/*
 * Internal use low level set hold state of call.
 * Returns nonzero if failed.
 */
static int callCtrlHoldSetLow(
        CallCtrlCallInfo *call,
        int hold
        )
{
    callCtrlUpdate();   /* may need UI update */
    assert(call != NULL);
    assert(call->state == CC_CALLINFO_ACTIVE||call->state == CC_CALLINFO_DISPLAYED);
    #if defined(CFG_CALL_STACK_HELLOSOFT)
    int err;
    if ( hold ) {
        if ( (err = hs_voip_hold_call(call->stackCallId)) != 0 ) {
            debugf("hs_voip_hold_call err %d\n", err );
            return 1;
        } else {
            call->holdOn = 1;
        }
    } else {
        if ( (err = hs_voip_unhold_call(call->stackCallId)) != 0 ) {
            debugf("hs_voip_unhold_call err %d\n", err );
            return 1;
        } else {
            call->holdOn = 0;
        }
    }
    #else
    #error Unimplemented
    #endif
    return 0;
}

/*
 * Put all calls on hold.
 */
int callCtrlHoldAll(void)
{
    int err = 0;
    for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
        CallCtrlCallInfo *call = &CallS.CallInfo[callIdx];
        if ( (call->state == CC_CALLINFO_ACTIVE ||
                call->state == CC_CALLINFO_DISPLAYED) && call->holdOn == 0 ) {
            if ( callCtrlHoldSetLow(call, 1) ) {
                debugf("callCtrlHoldAll: call hold failed\n");
                err = 1;
            }
        }
    }
    return err;
}

/*
 * Put an active call on/off hold, initiated by the local user.
 * Returns nonzero if failed.
 */
int callCtrlHoldSet(
        CallCtrlCallInfo *call,
        int hold               /* new value of hold on/off */
        )
{
    callCtrlUpdate();   /* may need UI update */
    int err = 0;
    /*
     * TODO: if this call is using the audio path then turn off
     * the audio
     */
    assert(call != NULL);
    if(call->state == CC_CALLINFO_ACTIVE ||
        call->state == CC_CALLINFO_DISPLAYED) {;}
    else {
        debugf("callCtrlHoldSet called with invalid state %d", call->state);
        return 1;
    }

    if ( !hold ) {
        /* We are to take off hold...
         * Put any other calls on hold (unless in same conference group)
         */
        for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
            CallCtrlCallInfo *call1 = &CallS.CallInfo[callIdx];
            if ( call1 != call && call1->holdOn == 0 &&
                    (call1->state == CC_CALLINFO_ACTIVE ||
                    call1->state == CC_CALLINFO_DISPLAYED ) &&
                    (call1->conferenced^call->conferenced) == 0 ) {
                err |= callCtrlHoldSetLow(call1, 1);
            }
        }
    }

    /*
     * Actually set this call's hold state, if it is different
     */
    if ( hold != call->holdOn ) {
        err |= callCtrlHoldSetLow(call, hold);
    }

    /*
     * Make sure all conferenced calls hold/unhold together */
    if ( call->conferenced ) {
        #if 0   // This doesn't work and probably isn't necessary
        for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
            CallCtrlCallInfo *call1 = &CallS.CallInfo[callIdx];
            if ( call1 != call && call1->conferenced && !call1->holdOn ) {
                if ( callCtrlHoldSetLow(call1, hold) ) {
                    err = 1;
                }
            }
        }
        #else
        if ( hold ) {
            debugf("Can't put conferenced call on hold");
        }
        return 1;
        #endif
    }

    if ( !call->holdOn ) {
        aumixCallOutMuteSet(call->muteOn);
    }

    return err;
}


/*
 * callCtrlStackHoldLocal -- protocol stack tells us whether we're on hold
 * or not locally...
 */
void callCtrlStackHoldLocal(
        unsigned stackCallId,
        int hold
        )
{
    callCtrlUpdate();   /* may need UI update */
    CallCtrlCallInfo *call = callCtrlFindCallByStackId( stackCallId );
    if ( ! call ) return;
    /* FIXME BUG: We also set holdOn when app issued a command to us to do so;
     * that should be considered anticipatory...
     * but we can have race conditions...
     */
    call->holdOn = hold;
    if( hold == 0 )
    {   /* Switch audio to call we are releasing hold from */
        callCtrlAudioOff();
        callCtrlAudioOn(call);
    } else {
        /* check for call transfer that was awaiting hold */
        callCtrlStackTransferCall(stackCallId);
        /* check for the second outbound call that was awaiting hold */
        callCtrlCheckForNewOutboundCall();
    }
    return;
}


/*
 * callCtrlStackHoldRemote -- protocol stack tells us whether we're on hold
 * or not by remote peer...
 */
void callCtrlStackHoldRemote(
        unsigned stackCallId,
        int hold
        )
{
    callCtrlUpdate();   /* may need UI update */
    CallCtrlCallInfo *call = callCtrlFindCallByStackId( stackCallId );
    if ( ! call ) return;
    call->remoteHoldOn = hold;
    if( hold == 0 )
    {   /* switch audio to call that hold was released from ... ? */
        /* Hmmm.... perhaps we should do this only if no current call audio?*/
        callCtrlAudioOff();
        callCtrlAudioOn(call);
    }
    return;
}


/*
 * Put a call on/off mute, initiated by the local user
 * Returns nonzero if failed.
 */
int callCtrlMuteSet(
        CallCtrlCallInfo *call,
        int mute               /* new value of mute on/off */
        )
{
    callCtrlUpdate();   /* may need UI update */
    int err = 0;
    /*
     * TODO: if this call is using the audio path then turn off
     * the audio
     *
     * TODO: tell the protocol stack to hold
     */
    assert(call != NULL);
    if ( call->state != CC_CALLINFO_DISPLAYED ) {
        return err;
    }
    if ( call->conferenced ) {
        for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
            CallCtrlCallInfo *call1 = &CallS.CallInfo[callIdx];
            if ( call1->conferenced ) call1->muteOn = mute;
        }
    } else {
        call->muteOn = mute;
    }

    if ( ! call->holdOn ) {
        aumixCallOutMuteSet(call->muteOn);
    }
    return err;
}


/*
 * Returns nonzero if callCtrlConference() would fail.
 */
avmerrCode callCtrlConferencePossible(void)
{
    if ( CallS.conferenceCount ) {
        debugf("callCtrlConferencePossible: conference already in progress (%d)\n",
            CallS.conferenceCount );
        return avmerr_Conference_Already;
    }
    #if CFG_CALL_CONFERENCE_WITH_HIGH_COMPRESSION_ENABLE() == 0
#if 0	// TODO
    if ( CallS.Config.EnableHighCompression == callCtrlCompression_eHigh ) {
        debugf("callCtrlConferencePossible: high compression is on, may not be enough cycles\n");
        return avmerr_Conference_HighCompression;
    }
#endif
    #endif
    #if defined(CFG_CALL_STACK_HELLOSOFT)
    /* The hellosoft document hs_voip_app_if.doc lists many restrictions
     * on making conference calls. The first call passed has to be active
     * and the second call has to be held, with some restrictions on how
     * it is held....
     */
    CallCtrlCallInfo *callActive = NULL;
    CallCtrlCallInfo *callHeld = NULL;

    for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
        if(CallS.CallInfo[callIdx].holdOn == 1)
        {
            callHeld = &CallS.CallInfo[callIdx];
            break;
        }
    }
    for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
        if(CallS.CallInfo[callIdx].holdOn == 0)
        {
            callActive = &CallS.CallInfo[callIdx];
            break;
        }
    }

    if( ( callActive == NULL ) || (callHeld == NULL) )
    {
        return avmerr_Conference_Need2Calls;
    }
    #endif

    return 0;
}

/* Join two calls with us in conference */
avmerrCode callCtrlConferenceBySlot(
        int WhichCall1,         /* active, unheld call */
        int SequenceNumber1,    /* 0 or must match that of call1 */
        int WhichCall2,         /* active but held call */
        int SequenceNumber2     /* 0 or must match that of call2 */
        )
{
    avmerrCode err = callCtrlConferencePossible();
    if ( err ) return err;

    callCtrlUpdate();   /* may need UI update */

    CallCtrlCallInfo *call1 = NULL;
    CallCtrlCallInfo *call2 = NULL;
    err =
        callCtrlFindCallBySlotOrSequence(&call1, WhichCall1, SequenceNumber1);
    if ( err ) {
        debugf("callCtrlConferenceBySlot: call idx %d + seq %d invalid!",
            WhichCall1, SequenceNumber1);
        return err;
    }
    err =
        callCtrlFindCallBySlotOrSequence(&call2, WhichCall2, SequenceNumber2);
    if ( err ) {
        debugf("callCtrlConferenceBySlot: call idx %d + seq %d invalid!",
            WhichCall2, SequenceNumber2);
        return err;
    }
    if ( call1 == call2 ) {
        return avmerr_Conference_Need2Calls;
    }
    if ( call1->holdOn ) {
        return avmerr_Conference_NeedFirstCallActive;
    }
    if ( ! call2->holdOn ) {
        return avmerr_Conference_NeedSecondCallHeld;
    }
    if ( (err = hs_voip_start_conference(
            call1->stackCallId, call2->stackCallId)) != 0 ) {
        debugf("callCtrlConference: hs_voip_start_conference ERROR %d\n", err);
        /* Actually, hellosoft code will mostly likely be negative... */
        err = avmerrCodeMake( avmerrComponent_HelloSoft,
                avmerrHelloSoft_Conference, err );
    } else {
        call1->conferenced = 1;
        call2->conferenced = 1;
        CallS.conferenceCount = 2;
    }
    #if defined(CFG_CALL_STACK_HELLOSOFT)
    #else
    #error Unimplemented
    #endif
    return err;
}


/*
 * Connect all (two) calls into a conference.
 * Currently UI and h.s. stack supports only max two calls so we can ignore
 * issue e.g. connecting > 2 users in conference call.
 * Returns nonzero in case of failure.
 * A current side effect is that, in case of failure, the first call
 * may have been taken off of hold (if on hold) and the second call
 * put on hold (if off of hold).
 */
avmerrCode callCtrlConference(void)
{
    if(CallS.CallInfo[0].holdOn == 0)
        return callCtrlConferenceBySlot( 0, 0, 1, 0 );
    return callCtrlConferenceBySlot( 1, 0, 0, 0 );
}


/*
 * Callback from protocol stack when conference started... automatically
 * takes conference calls off of hold...
 */
void callCtrlConferenceStarted(void)
{
    callCtrlUpdate();   /* may need UI update */
    for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
        CallCtrlCallInfo *call = &CallS.CallInfo[callIdx];
        if ( call->conferenced ) {
            #if defined(CFG_CALL_STACK_HELLOSOFT)
            call->holdOn = 0;
            #else
            #error Unimplemented
            #endif
        }
    }
    callCtrlUpdate();
}

/*
 * Local user pressed a numeric key, so need to generate dtmf tones
 *
 * We generate a local dtmf tone to the speaker/receiver for feedback.
 *
 * Hmmm.... as implemented in UI, the dtmf tone is generated when
 * pressing key, not here... so this should be removed.
 */
void callCtrlLocalDTMFStart(CallCtrlCallInfo *call, int dtmfLow, int dtmfHigh)
{
    assert(call != NULL);

    /* TODO */
}

/*
 * Local user released a numeric key, so stop dtmf tones
 *
 * This should be removed, see note for DTMFStart.
 */
void callCtrlLocalDTMFStop(CallCtrlCallInfo *call)
{
    assert(call != NULL);

    /* TODO */
}

/* Get ourselves registered so we can be called! */
void callCtrlSipRegister(void)
{
	printf("In callCtrlSipRegister...\n");
    if ( CallS.IsUp && callCtrlCheckResolve()==0)
    {
        #if defined(CFG_CALL_STACK_HELLOSOFT)
        int hsError;
        debugf("callCtrlSipRegister: Begin");
        // hs_int32
        // hs_voip_sip_register_req
        //    (
        //       hs_voip_identity_t *caller,
        //       hs_voip_identity_t *contact_info,
        //       hs_uint32   reg_validity_timeout
        //    )
        // typedef struct hs_voip_identity
        // {
        //    hs_char uri[HS_VOIP_SIP_URL_SIZE];
        //    hs_char display_name[HS_VOIP_SIP_DISPLAY_NAME_SIZE];
        // } hs_voip_identity_t;
        hs_voip_identity_t identity = {};
        sprcpy( identity.uri, sizeof(identity.uri),
            CallS.Current.OurPhoneNumber);
        sprcpy( identity.display_name, sizeof(identity.display_name),
            CallS.Current.OurPhoneNumber);
        hsError = hs_voip_sip_register_req(
            &identity,
            NULL,       // contact_info
            hs_voip_cm_get_registration_timeout());
        if ( hsError != 0 )
        {
            debugf("callCtrlSipRegister: hs_voip_sip_register_req error %d\n",
                hsError );
        } else {
            debugf("callCtrlSipRegister: registration process started.");
        }

		CallS.Registered = false;
		zcom_IndicateState(Step_SIP,
			State_SIP_Registering,
			ErrorCode_SIP_NOERROR
			);

        #else
        #error No protocol stack defined (CFG_CALL_STACK_....)
        #endif

    }

}

/* callback on timeout to periodically re-subscribe for message waiting indicator */
void callCtrlReSubscribeMwiTask(struct pollerTask *taskP)
{
    callCtrlSipSubscribeMwi();
}


/* Call when making new call if other call is dead or on hold,
 * or called back when hellosoft has put a call on hold.
 * We check to see if this we are supposed to make a call.
 * (which may or may not be the case).
 */
void callCtrlCheckForNewOutboundCall(void)
{
    CallCtrlCallInfo *call = CallS.pendingCall;
    if ( call == NULL )
    {
        // debugf("callCtrlCheckForNewOutboundCall: No new call should be started\n");
        return ;
    }

    /*
     * TODO: call the call protocol stack to initiate the call
     */
    #if defined(CFG_CALL_STACK_HELLOSOFT)
    int hsError;
    // typedef struct hs_voip_identity
    // {
    //    hs_char uri[HS_VOIP_SIP_URL_SIZE];
    //    hs_char display_name[HS_VOIP_SIP_DISPLAY_NAME_SIZE];
    // } hs_voip_identity_t;
    hs_voip_identity_t ourIdentity = {};
    sprcpy( ourIdentity.uri, sizeof(ourIdentity.uri),
        CallS.Current.OurPhoneNumber);
    sprcpy( ourIdentity.display_name, sizeof(ourIdentity.display_name),
        CallS.Current.OurPhoneNumber);
    hs_voip_identity_t theirIdentity = {};
    sprcpy( theirIdentity.uri, sizeof(theirIdentity.uri),
        call->phoneNum);
    // hs_int32
    // hs_voip_make_call
    //    (
    //       hs_voip_identity_t     *caller,
    //       hs_voip_identity_t     *callee,
    //       hs_voip_identity_t     *a_identity,
    //       hs_uint32              privacy
    //
    //    )
    hsError = hs_voip_make_call(
        &ourIdentity,
        &theirIdentity,
        NULL,           // a_identity
        0               // privacy
        );

    #else
    #error No protocol stack defined (CFG_CALL_STACK_....)
    return;
    #endif
    return;
}



/*
 * Initiate a new outbound call to the given number.  This is
 * typically initiated by the user via the UI.
 * To have greater assurance of success, call
 * callCtrlNewOutboundPossibleCheck() before calling this.
 *
 * Returns with *callPP set to the new call pointer, or NULL on error.
 * Returns 0 for success, or else avmerrCode.
 */
avmerrCode callCtrlNewOutboundCallAt(
        const char *phoneNum,           // input
        CallCtrlCallInfo *call,      // call handle, NOT NULL
        int SequenceNumber      // >= 0; 0 means assign one.
        )
{

    if(CallS.bMVQMode)
       return avmerr_Call_Failed_Reason_Call_Pending;

    callCtrlUpdate();   /* may need UI update */
    /*Sequence number of 0 means to make one up (must be positive) */
    if ( SequenceNumber == 0 ) SequenceNumber = --CallS.OutgoingSequenceNumber;
    call->SequenceNumber = SequenceNumber & 0x7fffffff /*must be positive*/;
    debugf("callCtrlNewOutboundCallAt seq=%d to %s\n",
        call->SequenceNumber, phoneNum);
    if ( CallS.pendingCall != NULL )
    {
        debugf("callCtrlNewOutboundCall: "
            "Can't start new call because a new call is pending\n");
        return avmerr_Call_Failed_Reason_Call_Pending;
    }
    CallS.pendingCall = call;
    call->LastErrCode = 0;      /* no error yet */
    call->state           = CC_CALLINFO_OUTBOUND_ALERT;
    call->ringing         = 0;
    call->stackCallId     = CALL_STACK_NULL_ID;         /* not known yet */
    call->alertStartTime  = dateTimeNow();
    call->activeStartTime = 0;
    call->outgoing        = 1;
    CallS.nOutgoingCalls++;
    call->holdOn          = 0;
    call->muteOn          = 0;
    sprcpy(call->phoneNum, sizeof(call->phoneNum), phoneNum);

	//for peer to peer
	//by Mike
	strcpy(CallS.hostIP, "");
	call->useP2P = CallS.useP2P;
	{
		//fill
		strcpy(call->hostIP, CallS.hostIP);
		call->hostPort = CallS.hostPort;
	}

    callCtrlAlertUpdate(call);

    /* Must put any previous calls on hold */
    CallCtrlCallInfo *call2 = &CallS.CallInfo[!call->idx];
    switch ( call2->state ) {
        case CC_CALLINFO_NOTUSED         :
        case CC_CALLINFO_OUTBOUND_ALERT  :
        case CC_CALLINFO_INBOUND_ALERT   :
        break;
        case CC_CALLINFO_ACTIVE          :
        case CC_CALLINFO_DISPLAYED       :
            if(call2->holdOn == 0)
            {
                callCtrlHoldAll();
                return 0;
            }
        break;
        case CC_CALLINFO_ZOMBIE          :
        break;
    }

    /* Share code... */
    callCtrlCheckForNewOutboundCall();

    return 0;
}

/*
 * Initiate a new outbound call to the given number.  This is
 * typically initiated by the user via the UI.
 * To have greater assurance of success, call
 * callCtrlNewOutboundPossibleCheck() before calling this.
 *
 * Returns with *callPP set to the new call pointer, or NULL on error.
 * Returns 0 for success, or else a avmerrCode
 *
 * Sequence number of 0 means make one up.
 */
avmerrCode callCtrlNewOutboundCall(
        const char *phoneNum,           // input
        CallCtrlCallInfo **callPP,      // output -- NULL or call handle
        int SequenceNumber              // number to apply to call, >= 0
        )
{
    callCtrlUpdate();   /* may need UI update */
    *callPP = NULL;             // default, bad
    debugf("callCtrlNewOutboundCall to %s\n", phoneNum);
    if ( CallS.pendingCall != NULL )
    {
        debugf("callCtrlNewOutboundCall: "
            "Can't start new call because a new call is pending\n");
        return avmerr_Call_Failed_Reason_Call_Pending;
    }

	//save, by Mike
	int useP2P = CallS.useP2P;

	//by Mike
	//need check config again...
	CallS.useP2P = 0;
	avmerrCode err = callCtrlNewOutboundPossibleCheck();
	if(err)
	{
		//set back, by Mike
		CallS.useP2P = useP2P;

		return err;
	}

	//More check here...
	//by Mike
	if(strcmp(phoneNum, CallS.Config.OurPhoneNumber)==0)
	{
		return avmerr_Call_Failed_Reason_In_Use;
	}

    CallCtrlCallInfo *call = callCtrlFindCallByState(CC_CALLINFO_NOTUSED);
    if ( call == NULL )
    {
        debugf("callCtrlNewOutboundCall: call table full.\n");
        return avmerrCallCtrl_CallFailed_TooMany;
    }
    *callPP = call;
    return callCtrlNewOutboundCallAt( phoneNum, call, SequenceNumber );
}

// callCtrlDtmfSend -- send dtmf digits in packetized form to current call.
// Returns nonzero if not handled.
int callCtrlDtmfSend(
    int digit           /* ascii char '0' to '9' or '*' or '#' */
    )
{
    #if defined(CFG_CALL_STACK_HELLOSOFT)
    hs_voip_app_telephone_event_t event;
    if ( digit >= '0' && digit <= '9' )
        event = HS_VOIP_APP_DTMF_0 + (digit - '0');
    else
    if ( digit == '*' ) event = HS_VOIP_APP_DTMF_STAR;
    else
    if ( digit == '#' ) event = HS_VOIP_APP_DTMF_HASH;
    else {
        return 1;
    }
    hs_voip_identity_t ourIdentity = {};
    sprcpy( ourIdentity.uri, sizeof(ourIdentity.uri),
        CallS.Current.OurPhoneNumber);
    sprcpy( ourIdentity.display_name, sizeof(ourIdentity.display_name),
        CallS.Current.OurPhoneNumber);
    hs_voip_send_telephone_event(
        &ourIdentity,
        event
        );
    return 0;
    #else
    return 1;
    #endif
}


// callCtrlHandoffCall -- attempt to join two calls, minus us.
//     If there is an immediate nonzero return, then the calls are entirely
//     unaffected.
//     Otherwise, the calls will either be transferred and terminated,
//     or %%%%%%%%% ?
//     The call is automatically put on hold while being transferred?
int callCtrlHandoffCall( void )
{
    callCtrlUpdate();   /* may need UI update */
    /* attended transfer */
    if ( (CallS.CallInfo[0].state == CC_CALLINFO_DISPLAYED) &&
         (CallS.CallInfo[1].state == CC_CALLINFO_DISPLAYED) &&
         (CallS.conferenceCount == 0))
    {
        /* we can do call transfer */
        CallS.attendedTransfer = 1;
        CallS.CallInfo[0].transferOn = 0;
        CallS.CallInfo[1].transferOn = 0;
        CallS.CallInfo[0].TransferPhoneNumber[0] = 0;
        CallS.CallInfo[1].TransferPhoneNumber[0] = 0;
        callCtrlHoldAll();
        if ( CallS.CallInfo[0].holdOn && CallS.CallInfo[1].holdOn ) {
            callCtrlStackTransferCall(CallS.CallInfo[0].stackCallId);
            return 0;
        }
        /* Come back when all on hold */
        return 0;
    }
    else
    {
        /* we can not do call transfer */
        /* we can not do call transfer */
        debugf("callCtrlHandoffCall: fail state0=%d state1=%d conf=%d",
            CallS.CallInfo[0].state,
            CallS.CallInfo[1].state,
            CallS.conferenceCount );
        return 1;
    }
}


// callCtrlTransferCall -- attempt to transfer a call.
//     If there is an immediate nonzero return, then the call is entirely
//     unaffected.
//     Otherwise, the call will either be transferred and terminated,
//     or %%%%%%%%% ?
//     The call is automatically put on hold while being transferred?
//
//     Pass phoneNum NULL for attended transfer, which means
//     to combine the two calls we have and remove ourselves from
//     both calls.
int callCtrlTransferCall(
        CallCtrlCallInfo *call,  // call to transfer
        const char *phoneNum     // NULL or phone no. to transfer to
        )
{
    callCtrlUpdate();   /* may need UI update */
    if ( !call || ! phoneNum || !*phoneNum )
        {
        debugf("callCtrlTransferCall: invalid arg.");
            return 1;
        }
    if ( CallS.pendingTransfer ) {
        debugf("callCtrlTransferCall: another call being transfered\n");
        return 1;
    }
    if ( call->state != CC_CALLINFO_DISPLAYED ) {
        debugf("callCtrlTransferCall: call not in displayed state\n");
        return 1;
    }
        call->transferOn = 1;
        CallS.pendingTransfer = call;
    CallS.attendedTransfer = 0;
    sprcpy( call->TransferPhoneNumber, sizeof(call->TransferPhoneNumber),
        phoneNum );
    callCtrlHoldAll();          // at least we should hold this call
    if ( ! call->holdOn ) {
        /* If not held, we'll have to come back when this is held */
        return 0;
    }
    callCtrlStackTransferCall(call->stackCallId);
    return 0;
}


/*
 * Call back from protocol stack when forwarding is done.
 * If it failed, do we get the call back?
 */
void callCtrlTransferDone(
        unsigned stackCallId,
        int fail                        // nonzero if failed
        )
{
    debugf("callCtrlTransferDone: ....\n");
    callCtrlUpdate();   /* may need UI update */
    CallS.pendingTransfer = NULL;
    CallCtrlCallInfo *call = callCtrlFindCallByStackId( stackCallId );
    if ( ! call ) return;
    memset( call->TransferPhoneNumber, 0x0, sizeof(call->TransferPhoneNumber));
    call->transferOn = 0;
    return;
}


static void callCtrlUriToName(
        char *name,
        int nameSize,           /* sizeof name storage */
        const char *uri                 /* typically of form: sip:xxx@yyy */
        )
{
    /* Chop off "sip:" prefix */
    if ( !strncmp(uri, "sip:", 4) ) uri += 4;
    /* Chop off @domainname suffix ... we only support calls through
     * our servers anyway.
     */
    const char *Ampersand = strchr(uri,'@');
    if ( Ampersand ){
        /* strip ampersand and what follows since it is our proxy server */
        sprncpy(name, nameSize, uri, Ampersand-uri );
    } else {
        sprcpy(name, nameSize, uri);
    }
    return;
 }


/* Called back when hellosoft has put a call on hold.
 * We check to see if this we are supposed to transfer the call
 * (which may or may not be the case).
 */
void callCtrlStackTransferCall(
        unsigned stackCallId
        )
{
    callCtrlUpdate();   /* may need UI update */
    CallCtrlCallInfo *call2 = NULL;
    CallCtrlCallInfo *call = callCtrlFindCallByStackId( stackCallId );
    if ( ! call ) return;

    if( CallS.attendedTransfer ) {
        for ( int callIdx = 0; callIdx < CFG_CALL_MAX; callIdx++ ) {
            if ( ( &CallS.CallInfo[callIdx] != call ) &&
                 (CallS.CallInfo[callIdx].holdOn == 1) &&
                 (CallS.CallInfo[callIdx].state == CC_CALLINFO_DISPLAYED))
                call2 = &CallS.CallInfo[callIdx];
        }
        if ( call2 ) {
            hs_voip_identity_t theirIdentity = {};
            if( CallS.SipDomainPort )
            {
            spr( theirIdentity.uri, sizeof(theirIdentity.uri),
                "sip:%s@%s:%d",
                call->phoneNum,
                    CallS.SipDomainName,
                    CallS.SipDomainPort);
            }
            else
            {
                spr( theirIdentity.uri, sizeof(theirIdentity.uri),
                    "sip:%s@%s",
                    call->phoneNum,
                    CallS.SipDomainName);
            }
            sprcpy( theirIdentity.display_name,
                sizeof(theirIdentity.display_name), call->phoneNum);
            hs_voip_transfer_call( call2->stackCallId,
                &theirIdentity,
                HS_VOIP_ATTENDED_XFER,
                NULL,
                0 );
        }
    } else if( call->transferOn && (call == CallS.pendingTransfer) ) {
        hs_voip_identity_t theirIdentity = {};
        if( CallS.SipDomainPort )
        {
        spr( theirIdentity.uri, sizeof(theirIdentity.uri),
            "sip:%s@%s:%d",
                call->TransferPhoneNumber,
                CallS.SipDomainName,
                CallS.SipDomainPort);
        }
        else
        {
            spr( theirIdentity.uri, sizeof(theirIdentity.uri),
                "sip:%s@%s",
                call->TransferPhoneNumber,
                CallS.SipDomainName);
        }
        sprcpy( theirIdentity.display_name,
            sizeof(theirIdentity.display_name),
            call->TransferPhoneNumber);
        hs_voip_transfer_call( call->stackCallId,
            &theirIdentity,
            HS_VOIP_BLIND_XFER,
            NULL,
            0 );
    }
    return;
}

/* Called back from hellosoft when call has been transferred by remote end.
 * We'll have a new remote end to talk to...
 */
void callCtrlStackCallTransfered(
        unsigned stackCallId,
        unsigned stackCallIdNew,
        const char *uri,
        const char *display_name
        )
{
    callCtrlUpdate();   /* may need UI update */
    debugf("callCtrlStackCallTransfered: Stack id %x new stack id %x uri:%s display_name:%s\n",
                   stackCallId,
                   stackCallIdNew,
                   uri,
                   display_name);

    CallCtrlCallInfo *call = callCtrlFindCallByStackId( stackCallId );
    if ( ! call ) {
        debugf("callCtrlStackCallTransfered: Did not find stack id %x \n",
                   stackCallId );
        return;
    }
    /* This is essentially a new outgoing call... we will be called later
     * with a release of the old call, fortunately the hellosoft
     * stack id will be different.
     * The sequence of hellosoft events is:
     *
     * (old stackid) HS_VOIP_HOLD_CALL_IND  -- if not done already
     * (old stackid) HS_VOIP_TRANSFER_CALL_IND -- that leads to here.
     * (new stackid) HS_VOIP_CALL_INITIATED_IND
     * (old stackid) HS_VOIP_RELEASE_CALL_IND
     * (new stackid) HS_VOIP_RINGING_IND   -- if we're lucky
     *
     * Replace parameters...
     */
    assert(CallS.pendingCall==NULL);
    if ( call->outgoing ) CallS.nOutgoingCalls--;
    else CallS.nIncomingCalls--;
    /* Set up new: */
    // %%%%% there are undoubtedly some bugs here
    // %%%%%%%% argh we lose the call log info for first call.
    callCtrlUriToName( call->phoneNum, sizeof(call->phoneNum), uri );
    /* Is the new display_name reliable? ... if so we should use it */
    #if 0
    memset( call->displayName, 0, sizeof(call->displayName));
    #else
    sprcpy(call->displayName, sizeof(call->displayName), display_name);
    #endif
    call->LastErrCode = 0;      /* no error yet */
    // NO: call->state           = CC_CALLINFO_OUTBOUND_ALERT;
    call->ringing         = 0;
    call->stackCallId     = stackCallIdNew;
    call->alertStartTime  = dateTimeNow();
    call->activeStartTime = 0;
    call->outgoing        = 1;
    CallS.nOutgoingCalls++;
    call->holdOn          = 0;
    call->remoteHoldOn    = 0;
    call->muteOn          = 0;
    return;
}

/* Our call is being forwarded to another remote peer.
 * Apparently ? this differs from blind transfer in that it occurs
 * before the other party has answered?
 */
void callCtrlStackForwardCall(
        unsigned stackCallId,
        const char *uri,
        const char *display_name
        )
{
    debugf("callCtrlStackForwardCall: Stack id %x uri:%s, display_name:%s \n",
                   stackCallId, uri, display_name);
    callCtrlUpdate();   /* may need UI update */

    CallCtrlCallInfo *call = callCtrlFindCallByStackId( stackCallId );
    if ( ! call ) return;
    CallS.pendingCall = call;
    callCtrlUriToName( call->phoneNum, sizeof(call->phoneNum), uri );
    /* Is the new display_name reliable? ... if so we should use it */
    #if 0
    memset( call->displayName, 0, sizeof(call->displayName));
    #else
    sprcpy(call->displayName, sizeof(call->displayName), display_name);
    #endif
    return;
}

/*
 * When the protocol stack accepts new outbound call, this is called
 * with the protocol stack call id as argument.
 * This may also be called with -1 to indicate that new outbound call
 * is rejected...
 */
void callCtrlOutboundCallAccepted(unsigned stackCallId)
{
    callCtrlUpdate();   /* may need UI update */
    CallCtrlCallInfo *call = CallS.pendingCall;
    if ( call == NULL )
    {   // hmmm... no outbound call pending that we know of...
        debugf("callCtrlOutboundCallAccepted: no pending call (normal during transfer)\n");
        return;
    }
    if ( stackCallId == CALL_STACK_NULL_ID )
    {   /* outbound call rejected by protocol stack */
        debugf("callCtrlOutboundCallAccepted: outbound call REJECTED\n");
        /* TODO: more information about why */
        call->LastErrCode = avmerr_Call_Failed_Unknown_Error;
        callCtrlTerminateLow( call );
        return;
    }
    // Here the pending call is accepted.
    call->stackCallId = stackCallId;
    debugf("callCtrlOutboundCallAccepted idx=%d stackIdx=%x\n",
        call->idx, stackCallId );
    CallS.pendingCall = NULL;

    /* Ringing is entirely up to ui */
}


void callCtrlRegistrationHandler(
        avmerrCode err,
        int expireSeconds
        )
{
	dbgf("In callCtrlRegistrationHandler\n");
    callCtrlUpdate();   /* may need UI update */
    CallS.regFailureReason = err;

    if ( /*err == 0 && */CallS.Deregistering )
    {
        CallS.Deregistering = 0;
		//Do reg once
		//Call directly, no need wait or notify reg-manager...
		//by Mike
		if(!CallS.useP2P)
		{
			callCtrlSipRegister();
		}
        return;
    }

	if (err==0)
	{
		zcom_IndicateState(Step_SIP,
			State_SIP_Ready,
			ErrorCode_SIP_NOERROR
			);
		CallS.Registered = true;
	}

    /* reschedule the register timer based on feeback value from sip server */
    if ( expireSeconds!=0 )
    {
		//by Mike
		if(CallS.Registered)
		{
			CallS.expireSeconds = expireSeconds/2;
			//Notify Reg-Manager...
			CallS.curRegNotifyState = _REG_NOTIFY_STATE_REGISTERED;
		}

        if( !CallS.StartSubscribe )
        {
            CallS.StartSubscribe = 1;
            if( CallS.DottedVoiceMailServerAddressString[0] )
            {
                pollerTaskReady(&CallS.ReSubscribeMwiTask);
            }
        }
    }
    else
    {   /* fail.... retried after 5 second */
		enumErrorCode	ErrorCode = ErrorCode_SIP_UNKNOWN;

		switch(err)
		{
		case avmerrHelloSoft_Registration_PreviousPending:
			ErrorCode = ErrorCode_SIP_Previous_Pending;
			break;
		case avmerrHelloSoft_Registration_Timeout:
			ErrorCode = ErrorCode_SIP_Timeout;
			break;
		case avmerrHelloSoft_Registration_AddressError:
			ErrorCode = ErrorCode_SIP_Address_Error;
		    break;
		default:
		    break;
		}
		zcom_IndicateState(Step_SIP, State_SIP_Idle, ErrorCode);

    }
}

avmerrCode callCtrlRegstrationStatusGet( void )
{
    /* NOTE: avmerr_Registration_Pending isn't really an error */
    return CallS.regFailureReason;
}

void callCtrlSubscribeMwiHandler(
            avmerrCode err,
            unsigned expireSeconds
            )
{
    /* reschedule the subscribe timer based on feeback value from sip server */
    if ( err == 0 ) {
        /* resched after feeback second - 3 second */
        if(expireSeconds != 0) {
            pollerTaskLongTimeoutRelSeconds(&CallS.ReSubscribeMwiTask, expireSeconds-3);
            debugf("SubscribeMwi timer change into %d second\n", expireSeconds);
        } else {
            pollerTaskLongTimeoutRelSeconds(&CallS.ReSubscribeMwiTask, 3600-3);
        }
    }
    else
    {   /* fail.... retried after 5 second */
        debugf("SubscribeMwi timer restart after 5 second (err 0x%x)", err);
        pollerTaskLongTimeoutRelSeconds(&CallS.ReSubscribeMwiTask, 90); //5
    }
    //callCtrlUpdate();   /* ui may want to know about this */
}

// called from voip stack with voice mail message waiting indication
void callCtrlVoiceMailWaitingSet(
    int NNewMsgs,
    int NOldMsgs,
    int NNewUrgentMsgs,
    int NOldUrgentMsgs
    )
{
    CallS.VoiceMailStatus.NNewMsgs = NNewMsgs;
    CallS.VoiceMailStatus.NOldMsgs = NOldMsgs;
    CallS.VoiceMailStatus.NNewUrgentMsgs = NNewUrgentMsgs;
    CallS.VoiceMailStatus.NOldUrgentMsgs = NOldUrgentMsgs;
    callCtrlUpdate();   /* may need UI update */
}

// Called from application to poll voice mail waiting status
// (If voice mail is not enabled, this returns all zeroes).
void callCtrlVoiceMailWaitingGet(
    int *NNewMsgsP,             /* NULL or output */
    int *NOldMsgsP,             /* NULL or output */
    int *NNewUrgentMsgsP,       /* NULL or output */
    int *NOldUrgentMsgsP        /* NULL or output */
    )
{
    if ( NNewMsgsP )   *NNewMsgsP = CallS.VoiceMailStatus.NNewMsgs;
    if ( NOldMsgsP )   *NOldMsgsP = CallS.VoiceMailStatus.NOldMsgs;
    if ( NNewUrgentMsgsP )   *NNewUrgentMsgsP = CallS.VoiceMailStatus.NNewUrgentMsgs;
    if ( NOldUrgentMsgsP )   *NOldUrgentMsgsP = CallS.VoiceMailStatus.NOldUrgentMsgs;
}



/*
 * When the protocol stack terminates a call, this is called
 * with the protocol stack call id as argument.
 * This may also be called with -1 to indicate that new outbound call
 * is rejected?
 *
 * TODO: make reason indications available to the user.
 */
void callCtrlCallRejected(unsigned stackCallId, avmerrCode err)
{
    debugf("callCtrlCallRejected stackCallId=0x%08x, err=0x%x",
        stackCallId, err );
    callCtrlUpdate();   /* may need UI update */
    if ( stackCallId == CALL_STACK_NULL_ID )
    {   /* outbound call rejected by protocol stack */
        if ( err == 0 ) err = avmerr_Call_Failed_Unknown_Error;  /* force nonzero */
        CallCtrlCallInfo *call = CallS.pendingCall;
        if ( call == NULL )
        {   // hmmm... no outbound call pending that we know of...
            debugf("callCtrlCallRejected: phase error\n");
            return;
        }
        call->LastErrCode = err;
        callCtrlTerminateLow( call );
        return;
    }
    CallCtrlCallInfo *call = callCtrlFindCallByStackId( stackCallId );
    if ( call )
        {
            debugf("callCtrlCallRejected idx=%d stackIdx=%x\n",
                call->idx, stackCallId );

        call->LastErrCode = err;
            callCtrlTerminateLow(call);
            return;
        }
    if ( CallS.pendingCall )
    {   /* MUST be outbound call rejected by protocol stack */
        /* The hellosoft code sometimes does this...? */
        debugf(
            "callCtrlCallRejected: assuming pending call new stackIdx=%x\n",
            stackCallId);
        CallCtrlCallInfo *call = CallS.pendingCall;
        call->LastErrCode = err;
        call->stackCallId = stackCallId;
        callCtrlTerminateLow( call );
        return;
    }
    /* Orphan call... clean up anyway */
    debugf("callCtrlCallRejected: unknown call stackIdx=%x\n", stackCallId);
    callCtrlStackReleaseCall(stackCallId);
    return;
}

/*
 * Here when the other side is ringing the user (i.e. outgoing call)
 */
void callCtrlOutboundCallRinging(unsigned stackCallId)
{
    callCtrlUpdate();   /* may need UI update */
    CallCtrlCallInfo *call;

    /* Find which call has the given stackCallId */

    if( CallS.pendingCall )
    {
        call = CallS.pendingCall;
        CallS.pendingCall = NULL;
        call->stackCallId = stackCallId;
        call->ringing = 1;
        /*
         * Tell the UI to update call display etc.
         */
        callCtrlUpdate();
        return;
    }

    call = callCtrlFindCallByStackId( stackCallId );
    if ( call )
        {
            debugf("callCtrlCallRinging idx=%d stackIdx=%x\n",
                call->idx, stackCallId );
            /* Note: user of "ringing" should ensure that state is
             * "outbound alert".
            */
            call->ringing = 1;
            /*
            * Tell the UI to update call display etc.
            */
            callCtrlUpdate();
            return;
        }
    debugf("callCtrlCallRinging: unknown call stackIdx=%x\n", stackCallId);
    return;
}

/*
 * Here when the other side answers (i.e. outgoing call)
 */
void callCtrlOutboundCallAnswered(unsigned stackCallId)
{
    callCtrlUpdate();   /* may need UI update */
    CallCtrlCallInfo *call = callCtrlFindCallByStackId( stackCallId );
    if ( call )
        {
            debugf("callCtrlCallAccepted idx=%d stackIdx=%x\n",
                call->idx, stackCallId );
            callCtrlActivateCall(call);
            return;
        }
    debugf("callCtrlCallAccepted: unknown call stackIdx=%x\n", stackCallId);
    return;
}



/*
 * Handle a new inbound call.  This is typically initiated by
 * a remote caller via the call protocol stack.
 *
 * Returns the new call info, or NULL on error.
 */
CallCtrlCallInfo *callCtrlNewInboundCall(
        const char *fromPhoneNum,
        const char *fromDisplayName,
        unsigned stackCallId
        )
{
    callCtrlUpdate();   /* may need UI update */
    CallCtrlCallInfo *call = callCtrlFindCallByState(CC_CALLINFO_NOTUSED);

    if ( call == NULL )
    {
        debugf("callCtrlNewInboundCall: call table full.\n");
        return NULL;
    }
    int SequenceNumber = --CallS.IncomingSequenceNumber;
    debugf("callCtrlNewInboundCall seq=%d from %s callIdx %d stackid %x\n",
        SequenceNumber, fromPhoneNum, call->idx, stackCallId );

    sprcpy( call->displayName, sizeof(call->displayName), fromDisplayName );
    callCtrlUriToName( call->phoneNum, sizeof(call->phoneNum), fromPhoneNum );
    call->state           = CC_CALLINFO_INBOUND_ALERT;
    call->stackCallId     = stackCallId;
    call->outgoing        = 0;
    CallS.nIncomingCalls++;
    call->SequenceNumber = SequenceNumber;
    call->holdOn          = 0;
    call->alertStartTime  = dateTimeNow();
    call->activeStartTime = 0;

    callCtrlAlertUpdate(call);

    /*
     * TODO: add a timer to abandon the call if it is never answered
     */

    /* This should eventually result in (for hs) a call to
     *  hs_voip_answer_call() or hs_voip_release_call()...
     */
    return call;
}

/* called back from stack when a stackCallId is changed
 */
CallCtrlCallInfo *callCtrlReplaceCall(
        const char *fromPhoneNum,
        const char *fromDisplayName,
        unsigned stackCallId,
        unsigned stackCallIdNew)
{
    debugf("callCtrlReplaceCall oldstackid %x, newstackid %x, phonenum %s\n",
        stackCallId, stackCallIdNew, fromPhoneNum );
    callCtrlUpdate();   /* may need UI update */

    CallCtrlCallInfo *call = callCtrlFindCallByStackId( stackCallId );
    if ( ! call ) return call;
    if ( call->outgoing ) CallS.nOutgoingCalls--;
    else CallS.nIncomingCalls--;

    sprcpy( call->displayName, sizeof(call->displayName), fromDisplayName );
    callCtrlUriToName( call->phoneNum, sizeof(call->phoneNum), fromPhoneNum );
    call->state           = CC_CALLINFO_DISPLAYED;
    call->stackCallId     = stackCallIdNew;
    call->outgoing        = 0;
    CallS.nIncomingCalls++;
    call->holdOn          = 0;
    call->remoteHoldOn = 0;
    call->alertStartTime  = dateTimeNow();
    call->activeStartTime = dateTimeNow();

    callCtrlStackReleaseCall(stackCallId);

    #if defined(CFG_CALL_STACK_HELLOSOFT)
    hs_voip_answer_call(call->stackCallId);
    #else
    #error No protocol stack defined (CFG_CALL_STACK_....)
    #endif
    return call;
}


/*
 * Set a call to the active state
 *      Called from ui when user hits e.g. TALK button for incoming call.
 *      Called internally when outgoing call is answered.
 */
void callCtrlActivateCall(CallCtrlCallInfo *call)
{
    callCtrlUpdate();   /* may need UI update */
    assert(call != NULL);

    debugf("callCtrlActivateCall: call %d is now active\n", call->idx);
    callCtrlAudioOff();         // deactivate other call?
    callCtrlAudioOn(call);

    #if defined(CFG_CALL_STACK_HELLOSOFT)
    if ( call->state == CC_CALLINFO_INBOUND_ALERT )
    {
        /* Must put any previous calls on hold */
        callCtrlHoldAll();

        hs_voip_answer_call(call->stackCallId);
    }
    #else
    #error No protocol stack defined (CFG_CALL_STACK_....)
    #endif
    if(call->state == CC_CALLINFO_DISPLAYED)
    {
        /* the original call be transfered,
        * just set remoteHoldOn to 0 to activate keypad
        */
        call->remoteHoldOn = 0;
    }
    else
    {
        call->state           = CC_CALLINFO_ACTIVE;
        call->activeStartTime = dateTimeNow();
        call->activeStartTimeRV = DateTimeTimeNow();
    }

    // Note: ui code should call callCtrlCallIsDisplayed() which
    //          bumps state to CC_CALLINFO_DISPLAYED;
    // Note: audio paths already on, including microphone

    /*
     * Tell wlan to optimize power
     */
    if ( (CallS.nOutgoingCalls + CallS.nIncomingCalls) == 1 ) {
        nethwVoipCallModeSet(1);
    }
}

/*
 * Acknowledge that the call has gone active.
 * Called by ui when it finds (by poling) any new calls that have become active
 * including those outbound calls.
 */
void callCtrlCallIsDisplayed(CallCtrlCallInfo *call)
{
    assert(call != NULL);
    assert(call->state == CC_CALLINFO_ACTIVE);
    debugf("callCtrlActivateCall: call %d is now displayed\n", call->idx);
    call->state = CC_CALLINFO_DISPLAYED;
    return;
}


/* callCtrlCheckConfig -- returns nonzero if not sufficiently configured.
*       The UI uses this to tell user...
*/
int callCtrlCheckConfig(void)
{
    struct callCtrlVoipConfig *ConfigP = &CallS.Config;
    #if 0
    debugf("Registrar %s Proxy %s Stun %s phone %s\n",
           ConfigP->SipServerAddressString,
           ConfigP->ProxyServerAddressString,
           ConfigP->StunServerAddressString,
           ConfigP->OurPhoneNumber);
    #endif

	//by Mike
	if(!callCtrlGetP2PMode())
	{

		// TODO: is it reasonable to try to do voip if no sip server is specified?
		//  what about proxy server... is it required also?
		//  Perhaps if there is no proxy server specified we should use
		//  the sip server?
		if ( ConfigP->SipServerAddressString[0] == '\0') {
			debugf("Need to configure sip server address\n");
			return 1;
		}
		if ( ConfigP->SipServerPort == 0 ) {
			debugf("Need to configure sip server port\n");
			return 1;
		}
		if ( ConfigP->ProxyServerAddressString[0] == '\0') {
			debugf("Need to configure proxy server address\n");
			return 1;
		}
		if ( ConfigP->ProxyServerPort == 0 ) {
			debugf("Need to configure proxy server port\n");
			return 1;
		}

	}

    if ( ConfigP->OurPhoneNumber[0] == 0 ) {
        debugf("Need to configure our phone number\n");
        return 1;
    }

    return 0;   // READY! (at least configured anyway)
}

static avmerrCode callCtrlCheckResolveSIPCfg()
{
    /* (re)start domain name resolution as necessary */

	if ( CallS.Config.ProxyServerAddressString[0] == 0 && CallS.Config.SipServerAddressString[0] != 0 ) {
        sprcpy(CallS.Config.ProxyServerAddressString, sizeof(CallS.Config.ProxyServerAddressString),
            CallS.Config.SipServerAddressString );
    }

    if (CallS.Config.SipServerAddressString[0] == '\0')
    {
		debugf("Null resolve!\n");
        return avmerr_Call_Failed_Reason_Not_Configured;
    }

    if ( CallS.SipServerAddress == 0 ) {
        callCtrlResolverHelper(
            "Sip Registrar",
            &CallS.sipRegistrarResolver,
            CallS.Config.SipServerAddressString,
            &CallS.SipServerAddress,
            CallS.DottedSipServerAddressString
            );
    }

    if ( CallS.ProxyServerAddress == 0 ) {
        if ( strcmp(CallS.Config.SipServerAddressString,
                    CallS.Config.ProxyServerAddressString) ) {
            callCtrlResolverHelper(
                "Sip Proxy",
                &CallS.sipProxyResolver,
                CallS.Config.ProxyServerAddressString,
                &CallS.ProxyServerAddress,
                CallS.DottedProxyServerAddressString
                );
        } else
        if ( CallS.SipServerAddress != 0 ) {
            /* copy resolution from sip server */
            CallS.ProxyServerAddress = CallS.SipServerAddress;
            sprcpy(CallS.DottedProxyServerAddressString,
                sizeof(CallS.DottedProxyServerAddressString),
                CallS.DottedSipServerAddressString);
        }
    }

	if ( CallS.VoiceMailServerAddress == 0 &&
                CallS.Config.VoiceMailServerAddressString[0] )
	{
        if ( !strcmp(CallS.Config.SipServerAddressString,
                     CallS.Config.VoiceMailServerAddressString) )
		{
            if ( CallS.SipServerAddress ) {
                /* copy resolution from Sip server */
                CallS.VoiceMailServerAddress = CallS.SipServerAddress;
                sprcpy(CallS.DottedVoiceMailServerAddressString,
                    sizeof(CallS.DottedVoiceMailServerAddressString),
                    CallS.DottedSipServerAddressString);
            }
        }
        else if ( !strcmp(CallS.Config.ProxyServerAddressString,
                     CallS.Config.VoiceMailServerAddressString) )
		{
            if ( CallS.ProxyServerAddress )
			{
                /* copy resolution from Proxy server */
                CallS.VoiceMailServerAddress = CallS.ProxyServerAddress;
                sprcpy(CallS.DottedVoiceMailServerAddressString,
                    sizeof(CallS.DottedVoiceMailServerAddressString),
                    CallS.DottedProxyServerAddressString);
            }
        }
        else
    {
        callCtrlResolverHelper(
            "Voice Mail Server",
            &CallS.sipVoiceMailServerResolver,
            CallS.Config.VoiceMailServerAddressString,
            &CallS.VoiceMailServerAddress,
            CallS.DottedVoiceMailServerAddressString
            );
    }
    }

    /* Report if we haven't resolved...
     */
    if ( CallS.SipServerAddress == 0 ) {
        debugf("Waiting for resolution of sip server address");
     ///   zcom_IndicateState(Step_SIP, State_SIP_Registering, ErrorCode_SIP_SIPSERVER_ADDRESS_RESOLVER_ERROR);
        return avmerr_Call_Failed_Reason_NeedSipServerResolve;
    }
    if ( CallS.ProxyServerAddress == 0 ) {
        debugf("Waiting for resolution of proxy server address");
     ///   zcom_IndicateState(Step_SIP, State_SIP_Registering, ErrorCode_SIP_PROXYSERVER_ADDRESS_RESOLVER_ERROR);
        return avmerr_Call_Failed_Reason_NeedProxyServerResolve;
    }

    return 0;

}

static avmerrCode callCtrlCheckResolveStunCfg()
{
	/*
	 * advance NAT traversal using STUN server as required,
     * report if that hasn't finished.
	*/

   if (CallS.Config.OutboundProxyServerAddressString[0] != '\0' )
	{
      //
      // Outbound proxy used
      //
      //ignore STUN!!!
      //by Mike
		hs_voip_cm_set_nat_type(HS_VOIP_NO_NAT);
		hs_fwk_nat_stun_stop();

      if (CallS.OutboundProxyServerAddress == 0)
		{
			callCtrlResolverHelper(
				"Outbound proxy server",
				&CallS.OutboundProxyServerResolver,
				CallS.Config.OutboundProxyServerAddressString,
				&CallS.OutboundProxyServerAddress,
				CallS.DottedOutboundProxyServerAddressString);
		}

		if (CallS.OutboundProxyServerAddress == 0)
		{
			//
			// Outbound proxy resolve fail
			//
		///	zcom_IndicateState(Step_SIP, State_SIP_Registering, ErrorCode_SIP_OUTBOUNDSERVER_ADDRESS_RESOLVER_ERROR);

			return avmerr_Call_Failed_Reason_NeedProxyServerResolve;	// Fail
		}
      return 0;
	}

   //use stun?
   if ( CallS.StunServerAddress == 0 &&
                CallS.Config.StunServerAddressString[0] ) {
        /* stun servers are never the same as sip servers, so
         * don't even try to share
         */
        callCtrlResolverHelper(
                "Stun server",
                &CallS.stunServerResolver,
                CallS.Config.StunServerAddressString,
                &CallS.StunServerAddress,
                CallS.DottedStunServerAddressString
                );
        if ( CallS.StunServerAddress != 0 ) callCtrlUpdate();
    }

    if ( CallS.Config.StunServerAddressString[0] != '\0' ) {
        // If stun server being used
        if (CallS.StunServerAddress==0) {
            debugf("Waiting for resolution of stun server address");
		///	zcom_IndicateState(Step_SIP, State_SIP_Registering, ErrorCode_SIP_STUNSERVER_ADDRESS_RESOLVER_ERROR);

			//
			// Stun resolve fail
			//
				return avmerr_Call_Failed_Reason_NeedStunServerResolve;	// Fail
        }
        else {
            #if defined(CFG_CALL_STACK_HELLOSOFT)
            if (hs_fwk_nat_stun_get_binding_type() == HS_FWK_NAT_TYPE_ERROR &&
                    !hs_fwk_nat_stun_is_start()) {
                hs_char  stun_server_ip_l[HS_VOIP_SIP_URL_SIZE];
                hs_char  local_ip_addr[HS_VOIP_IPADDR_LEN];
                struct in_addr servIp;
                hs_int16 stun_server_port_l = CallS.Current.StunServerPort;
                strcpy(local_ip_addr, lwipNetworkIpAddressStringGet());
                /* Read the STUN server settings from the configuation */
                servIp.s_addr = htonl(CallS.StunServerAddress);
                strcpy(stun_server_ip_l, inet_ntoa(servIp));
                hs_fwk_nat_stun_start(
                    local_ip_addr,
#if 1 //by Mike
                    HS_FWK_NAT_STUN_DEFAULT_PORT,
#else
					callCtrlGetLocalPort(),
#endif
                    stun_server_ip_l,
                    stun_server_port_l);
                hs_fwk_nat_stun_setup_binding(IP_ADDR_ANY, hs_voip_cm_get_sip_port());
                if (hs_fwk_nat_stun_get_binding_type() != HS_FWK_NAT_TYPE_ERROR) {
                    callCtrlUpdate();
                }
            }
            if (hs_fwk_nat_stun_get_binding_type() == HS_FWK_NAT_TYPE_ERROR) {
                debugf("Waiting for resolution of stun binding type");
           ///     zcom_IndicateState(Step_SIP, State_SIP_Registering, ErrorCode_SIP_STUNSERVER_BINDING_ERROR);
                return avmerr_Call_Failed_Reason_NeedNatTraversal;
            }
            #else       // CFG_CALL_STACK_....
            #error No protocol stack defined (CFG_CALL_STACK_....)
            #endif

        }
    }
	 else
    {
		  hs_voip_cm_set_nat_type(HS_VOIP_NO_NAT);
		  hs_fwk_nat_stun_stop();
    }

    return 0;   // READY!

}


/* callCtrlCheckResolve -- returns nonzero if servers not resolved etc.
 *       The UI uses this to tell user... and this is used from
 *       polling to get resolution further along.
 */
avmerrCode callCtrlCheckResolve(void)
{
    avmerrCode err0, err1;

    err1 = callCtrlCheckResolveStunCfg();

    if (!CallS.useP2P)
    {
        err0 = callCtrlCheckResolveSIPCfg();

        if (err0)
        {
            debugf("[callCtrlCheckResolve] callCtrlCheckResolveSIPCfg error\n");
            return err0;
        }
    }

    if (err1)
    {
        debugf("[callCtrlCheckResolve] callCtrlCheckResolveStunCfg error\n");
        return err1;
    }

    return 0;   // READY!
}

//for Peer to Peer
//by Mike
void callCtrlSetP2PMode(bool useP2P)
{
	CallS.useP2P = useP2P;
}

bool callCtrlGetP2PMode()
{
	return CallS.useP2P;
}

void callCtrlSetHostIP(char * ip)
{
	if(ip)
	{
		sprcpy(CallS.hostIP, sizeof(CallS.hostIP), ip);
	}
	else
	{
		strcpy(CallS.hostIP, "");
	}
}

char * callCtrlGetHostIP()
{
	return CallS.hostIP;
}

void callCtrlSetHostPort(unsigned port)
{
	if ( port>1024 && port<65000 )
		CallS.hostPort = port;
	else
		CallS.hostPort = 5060;
}

unsigned callCtrlGetHostPort()
{
	return CallS.hostPort;
}

void callCtrlSetLocalPort(unsigned port)
{
	if ( port>1024 && port<65000 )
		CallS.localPort = port;
	else
		CallS.localPort = 5060;
	//need set to cm...
	hs_voip_cm_set_sip_local_port(CallS.localPort);
}

unsigned callCtrlGetLocalPort()
{
	return CallS.localPort;
}

//for NATs
unsigned callCtrlGetNATMapIP()
{
	return CallS.mapIP;
}

void callCtrlSetNATMapIP(unsigned addr)
{
	CallS.mapIP = addr;
}

unsigned callCtrlGetNATMapPort()
{
	return CallS.mapPort;
}

void callCtrlSetNATMapPort(unsigned port)
{
	CallS.mapPort = port;
}

//for new P2P call issue
//by Mike
static avmerrCode callCtrlNewP2POutboundPossibleCheck(void)
{
	if ( ! nethwWlanConnectedGet() )
        return avmerr_Call_Failed_Reason_Wlan_Down;
    if ( ! lwipNetworkIsUp() )
        return avmerr_Call_Failed_Reason_Ip_Down;
	if ( callCtrlCheckConfig() )
		return avmerr_Call_Failed_Reason_Not_Configured;
	avmerrCode Reason = callCtrlCheckResolve();
    if ( Reason )
        return Reason;
	if ( ! CallS.IsUp)
        return avmerr_Call_Failed_Reason_Not_Ready;
	if(!CallS.useP2P)
	{
		//need not register anyway
		//not in P2P mode
		return avmerr_Call_Failed_Reason_Not_Ready;
	}
    if ( CallS.pendingCall != NULL )
        return avmerr_Call_Failed_Reason_Call_Pending;
    if ( (CallS.nOutgoingCalls + CallS.nIncomingCalls) > CFG_CALL_MAX )
        return avmerr_Call_Failed_Reason_Too_Many;

    return 0;
}

char * reverseStr(const char * str)
{
	static char revBuff[100];
	memset(revBuff, 0, 100);
	int len = strlen(str);
	if(len>99)
		len = 99;
    for(int i=0;i<len;i++)
	{
		revBuff[i] = str[len-1-i];
	}
    return revBuff;
}

avmerrCode callCtrlNewP2POutboundCall(
        const char *phoneNum,           // input
		const char *szIP,				//input
		unsigned  port,					// input
        CallCtrlCallInfo **callPP       // output -- NULL or call handle
        )
{
	 avmerrCode errcode;
	 int oldMode;
	 char IPs[100];
    *callPP = NULL;             // default, bad

    if(CallS.bMVQMode)
       return avmerr_Call_Failed_Reason_Call_Pending;

    if ( CallS.pendingCall != NULL )
    {
        debugf("callCtrlNewP2POutboundCall: "
            "Can't start new call because a new call is pending\n");
        return avmerr_Call_Failed_Reason_Call_Pending;
    }

	if(phoneNum==NULL || szIP==NULL || *szIP=='\0')
	{
		return avmerr_Call_Failed_Reason_Not_Configured; //common error
	}

	strcpy(IPs, szIP);
	if(!hs_fwk_is_ipv4_addr(IPs))
	{
		//Need to change to IP addr string by DDNS later
		//here we just do resolve by ourselves...
		dbgf("Not IP addr, do resolve...!\n");
		struct hostent * ht = gethostbyname(szIP);
		dbgf("ht: %p\n", ht);
        if(!ht || ht->h_addrtype != AF_INET || *ht->h_addr_list==NULL)
			return avmerr_Call_Failed_Unknown_Error;
		strcpy(IPs, reverseStr(ht->h_addr_list[0]));
        dbgf("Addr is : %s\n", IPs);

	}

	//more check here...
	//by Mike
	if(strcmp(phoneNum, CallS.Config.OurPhoneNumber)==0 && strcmp(IPs, callCtrlGetHostIP())==0)
	{
		return avmerr_Call_Failed_Reason_In_Use;
	}

	oldMode = CallS.useP2P;
	CallS.useP2P = 1;
	errcode = callCtrlNewP2POutboundPossibleCheck();
    if ( errcode )
    {   /* could happen due to race condition? */
        CallS.useP2P = oldMode;
		debugf("Failed to pass new P2P call check!\n");
        return errcode;
    }

    debugf("callCtrlNewP2POutboundCall to %s\n", phoneNum);

    CallCtrlCallInfo *call = callCtrlFindCallByState(CC_CALLINFO_NOTUSED);

    if ( call == NULL )
    {
        debugf("callCtrlNewP2POutboundCall: call table full.\n");
		CallS.useP2P = oldMode;
        return LANG_Call_Failed_Reason_Too_Many;
    }
    *callPP = call;
    CallS.pendingCall = call;
    //call->failed          = 0;
    //call->failMsg         = 0;
    call->state           = CC_CALLINFO_OUTBOUND_ALERT;
    call->ringing         = 0;
    call->stackCallId     = CALL_STACK_NULL_ID;         /* not known yet */
    call->alertStartTime  = dateTimeNow();
    call->activeStartTime = 0;
    call->outgoing        = 1;
    CallS.nOutgoingCalls++;
    call->holdOn          = 0;
    call->muteOn          = 0;
    sprcpy(call->phoneNum, sizeof(call->phoneNum), phoneNum);

	//for peer to peer
	//by Mike
	call->useP2P = CallS.useP2P;
	callCtrlSetHostPort(port);
	callCtrlSetHostIP(IPs);
	{
		//fill
		strcpy(call->hostIP, CallS.hostIP);
		call->hostPort = CallS.hostPort;
	}

    callCtrlAlertUpdate(call);

    /* Must put any previous calls on hold */
    CallCtrlCallInfo *call2 = &CallS.CallInfo[!call->idx];
    switch ( call2->state ) {
        case CC_CALLINFO_NOTUSED         :
        case CC_CALLINFO_OUTBOUND_ALERT  :
        case CC_CALLINFO_INBOUND_ALERT   :
        break;
        case CC_CALLINFO_ACTIVE          :
        case CC_CALLINFO_DISPLAYED       :
            if(call2->holdOn == 0)
    {
                callCtrlHoldAll();
                return 0;
            }
        break;
        case CC_CALLINFO_ZOMBIE          :
        break;
    }

    /* Share code... */
    callCtrlCheckForNewOutboundCall();

    return 0;
}

/*
 * Returns 0 if it is possible to make a new outbound call,
 * else returns language specific error code for display.
 * No new outbound calls may be made while one is pending or
 * if there are too many already, or if the system is not configured
 * or is not otherwise ready.
 */
avmerrCode callCtrlNewOutboundPossibleCheck(void)
{
    avmerrCode Reason;

	//by Mike
	if(callCtrlGetP2PMode())
	{
		return callCtrlNewP2POutboundPossibleCheck();
	}

	if ( ! nethwWlanConnectedGet() )
        return avmerr_Call_Failed_Reason_Wlan_Down;
    if ( ! lwipNetworkIsUp() )
        return avmerr_Call_Failed_Reason_Ip_Down;
	if ( callCtrlCheckConfig() )
		return avmerr_Call_Failed_Reason_Not_Configured;
    Reason = callCtrlCheckResolve();
    if ( Reason )
        return Reason;
#if 1 // tianjun, 2007-10-19 9:45:54
    if ( ! CallS.IsUp || !CallS.Registered)
        return avmerr_Call_Failed_Reason_Not_Ready;
#else
    if ( ! CallS.IsUp ) {
        return avmerr_Call_Failed_Reason_Not_Ready;
    }
    if ( ! CallS.Registered ) {
        return avmerr_Call_Failed_Reason_NeedRegistration;
    }
#endif
    if ( CallS.pendingCall != NULL )
        return avmerr_Call_Failed_Reason_Call_Pending;
    if ( (CallS.nOutgoingCalls + CallS.nIncomingCalls) >= CFG_CALL_MAX )
        return avmerr_Call_Failed_Reason_Too_Many;
    if ( CallS.regFailureReason )
        return CallS.regFailureReason;
    return 0;
}

//for caller-id on/off
//by Mike
int callCtrlGetOwnerShown()
{
	return CallS.OwnerShown;
}

void callCtrlSetOwnerShown(int show)
{
	CallS.OwnerShown = show;
}

unsigned int callCtrlGetExpire()
{
    return CallS.Current.Expire;
}

const char *callCtrlGetOurPhoneNumber(void)
{
    return CallS.Current.OurPhoneNumber;
}

#if defined(HANDSET)
// Init tones played to caller and/or user
static void callCtrlTonesInit(void)
{
    // Provide tone for "caller being recorded"
    // This is used as needed by aumix.
    auplayPlay2(
        &CallS.CallerIsBeingRecordedTone,       // tone lives here
        aumixVoiceSource_eCallerRecordingTone,
        auplayType_eTelephoneTone,
        telephoneToneType_eRecord,
        CFG_CALL_HALF_AMPLITUDE
        );
}
#endif // defined(HANDSET)

//static void callCtrlSetupRandomSipPort()
//{   /* setup the random port in order to avoid the conflict with other phone in same nat*/
//    hs_uint16 port;
//    hs_uint16 sport = randomlibGet();
//    printf("Setup a random port\n");
//    if (sport<=1024)
//    {
//        sport = (sport&0xff)+1024;
//    }
//    hs_voip_cm_set_sip_local_port(sport);
//
//    port = randomlibGet();
//    if ( port<=1024 || port>=65000 )
//    {
//        port = (port&0xff)+1024;
//    }
//    if ( port == sport )
//    {
//        port = sport + 1;
//    }
//    if ( (port % 2)!=0 )
//    {   /* rtp port should be odd number */
//        ++port;
//    }
//    hs_voip_cm_set_rtp_port_limits(port, 0xffff - port);
//}

/*
 * Called at least once during initialization
 * Last non-NULL uiUpdateCB wins.
 * The update callback must do no more than schedule a later update...
 * the changes to be updated may not have even been posted yet
 * when the update callback is called.
 */
void callCtrlInit(void (*uiUpdateCB)(void))
{
    int callIdx;

	//Do reg manager init first...
	//Maybe better put it in ui(like uiMainWin.c) [If removed, use the previous Makefile is OK]
	//here is just so-so!.
	//by Mike
	uiSipRegServiceInit();
	IMManager_Initialize();

    if ( CallS.IsInit ) {
        /* Later calls can modify the update callback */
        if ( uiUpdateCB ) CallS.uiUpdateCB = uiUpdateCB;
        return;
    }
    CallS.IsInit = 1;           /* once only */

    callCtrlEventLogInit();

    // Init our state structure
    CallS.Registered = false;
    CallS.audioCall = NULL;
    if ( uiUpdateCB ) CallS.uiUpdateCB = uiUpdateCB;
    CallS.pendingCall = NULL;
    for ( callIdx = 0 ; callIdx < CFG_CALL_MAX ; callIdx++ ) {
        CallCtrlCallInfo *call = &CallS.CallInfo[callIdx];
        call->state = CC_CALLINFO_NOTUSED;
        call->idx   = callIdx;
        call->stackCallId = CALL_STACK_NULL_ID;
    }
    #if defined(HANDSET)
    callCtrlTonesInit();
    #endif // defined(HANDSET)

    mainthreadTaskInit(
        &CallS.BringUpTask, "callCtrlBringUp", callCtrlBringUpTask );

    mainthreadTaskInit(
        &CallS.UiNotifyTask, "callCtrlUiNotify", callCtrlUiNotifyTask );
    mainthreadTaskInit(
        &CallS.ReSubscribeMwiTask, "callCtrlReSubscribeMwi", callCtrlReSubscribeMwiTask );

    /* Read from flash */
    fdbInit();
    if (fdbRecordRead(configFlashRecordId_VoipSetup,
        &CallS.Flash, sizeof(CallS.Flash)) != 0)
    {
        /* serious flash read error only.
        *       A missing record does not cause this error.
        */
        debugf("Flash configuration read error...............\n");
        /* ... struggle on... */
        // Zero out what we read from flash to avoid being confused
        // by garbage
        memset(&CallS.Flash, 0, sizeof(CallS.Flash));
    }
    if ( CallS.Flash.Version != CALL_VOIPCONFIG_VERSION ) {
        debugf("Wrong version %d in voip setup flash record\n",
            CallS.Flash.Version );
        // Zero out what we read from flash to avoid being confused
        // by garbage
        memset(&CallS.Flash, 0, sizeof(CallS.Flash));
        // Use defaults for our configuration
        memset(&CallS.Config, 0, sizeof(CallS.Config));
    } else {
		CallS.Config = CallS.Flash;
    }
    //if (fdbRecordRead(configFlashRecordId_AutoAnswer,
    //    &CallS.AnswerFlash, sizeof(CallS.AnswerFlash)) != 0)
    //{
    //    /* serious flash read error only.
    //    *       A missing record does not cause this error.
    //    */
    //    debugf("Flash autoanswer configuration read error...............\n");
    //    /* ... struggle on... */
    //    // Zero out what we read from flash to avoid being confused
    //    // by garbage
    //    memset(&CallS.AnswerFlash, 0, sizeof(CallS.AnswerFlash));
    //} else
    //if ( CallS.AnswerFlash.Version != CALLCTRL_AUTOANSWER_CONFIG_VERSION )
    //{
    //    debugf("Flash autoanswer configuration bad version %d\n",
    //        CallS.AnswerFlash.Version );
    //    /* ... struggle on... */
    //    // Zero out what we read from flash to avoid being confused
    //    // by garbage
    //    memset(&CallS.AnswerFlash, 0, sizeof(CallS.AnswerFlash));
    //}
    //memcpy( &CallS.Current.AnswerConfig, &CallS.AnswerFlash, sizeof(CallS.Current.AnswerConfig));
    /* fix up anything that needs to be defaulted */
    callCtrlVoipConfigDefaultGet(&CallS.Config);

    CallS.regFailureReason = avmerr_Registration_Pending;

	//by Mike
	if(!CallS.useP2P)
	{
    /* Resolving sip registrar and proxy is part of bringup: */
    aresJobInit(&CallS.sipProxyResolver);
    //aresJobSetWakeUpTask(&CallS.sipProxyResolver, &CallS.BringUpTask );
    aresJobInit(&CallS.sipRegistrarResolver);
    //aresJobSetWakeUpTask(&CallS.sipRegistrarResolver, &CallS.BringUpTask );
    aresJobInit(&CallS.OutboundProxyServerResolver);
	}

	//p2p also need stun server?
	//by Mike
	if(CallS.Config.StunServerAddressString[0])
	{
		aresJobInit(&CallS.stunServerResolver);
		//aresJobSetWakeUpTask(&CallS.stunServerResolver, &CallS.BringUpTask );
	}


    #if defined(CFG_CALL_STACK_HELLOSOFT)

    debugf("callCtrlInit initializing hellosoft protocol stack\n");
    callHsHelperInit();    // MUST do this before hs_voip_sys_init_internal
    // Now try to bring it up... will schedule later try if cannot now
    //callCtrlBringUpTask(&CallS.BringUpTask);

    #else       // CFG_CALL_STACK_....
    #error No protocol stack defined (CFG_CALL_STACK_....)
    #endif
}

/*
 * callCtrlAudioChangeCallbackInstall -- install callback that
 * is called with new audio state whenever it changes.
 * The audio state is on whenever we have calls up that can use audio,
 * even if muted, and off otherwise.
 */
void callCtrlAudioChangeCallbackInstall(
    void (*AudioChangeCB)(int AudioOn)  // callback on audio state change
    )
{
    CallS.AudioChangeCB = AudioChangeCB;
}

/*===========================================================================
 * avmerr / slot based interface,
 * for use with avmi
 */

/*
 * Initiate a new outbound call to the given number using given slot.
 * Returns 0 for success, or else avmerrCode
 */
avmerrCode callCtrlNewOutboundCallBySlot(
        const char *phoneNum,           // input
        int callIdx,                    // which call slot (0 or 1), or -1
        int SequenceNumber              // 0 or seq. no. to use, > 0
        )
{
    callCtrlUpdate();   /* may need UI update */

    CallCtrlCallInfo *call = NULL;
    debugf("callCtrlNewOutboundCallBySlot %d to %s", callIdx, phoneNum);
    if ( callIdx < 0 ) {
		//by Mike
		//need check config again...
		//save, by Mike
		int useP2P = CallS.useP2P;

		CallS.useP2P = 0;
		avmerrCode err = callCtrlNewOutboundPossibleCheck();
		if(err)
		{
			CallS.useP2P = useP2P;
			return err;
		}

        /* Find a free slot */
        call = callCtrlFindCallByState(CC_CALLINFO_NOTUSED);
        if ( call == NULL )
        {
            debugf("callCtrlNewOutboundCall: call table full.\n");
            return avmerr_Call_Failed_Reason_In_Use;
        }
        callIdx = call->idx;
    } else {
        /* Use given slot */
        if ( callIdx >= CFG_CALL_MAX ) {
            debugf("callCtrlNewOutboundCallBySlot: call idx %d invalid!", callIdx);
            return avmerr_Call_Failed_Reason_Bad_Slot;
        }
        call = &CallS.CallInfo[callIdx];
        if ( call->state != CC_CALLINFO_NOTUSED )
        {
            debugf("callCtrlNewOutboundCallBySlot: call idx %d used.", callIdx);
            return avmerr_Call_Failed_Reason_In_Use;
        }
    }
    /* Now we have a valid call unused slot!
     * Any errors after this can be reported in the call slot.
     */
    call->LastErrCode = 0;      /* no error yet */
    if ( SequenceNumber < 0 ) SequenceNumber = ++CallS.OutgoingSequenceNumber;
    SequenceNumber &= 0x7fffffff;       /* to be sure */
    call->SequenceNumber = SequenceNumber;
    avmerrCode err = callCtrlNewOutboundPossibleCheck();
    if ( err ) return call->LastErrCode = err;
    if ( ! CallS.IsUp ) {
        debugf("callCtrlNewOutboundCallBySlot: not ready for calls!");
        return call->LastErrCode = avmerr_Call_Failed_Reason_Not_Ready;
    }
    if ( CallS.pendingCall != NULL )
    {
        debugf("callCtrlNewOutboundCallBySlot: "
            "Can't start new call because a new call is pending\n");
        return call->LastErrCode = avmerr_Call_Failed_Reason_Call_Pending;
    }
    /* Shouldn't be any errors, but... */
    if ( callCtrlNewOutboundCallAt( phoneNum, call, SequenceNumber ) ) {
        return call->LastErrCode = avmerr_Call_Failed_Unknown_Error;
    }
    return 0;
}


/*
 * callCtrlTransferCallBySlot --
 * Transfer an ongoing call (slot callIdx1) to a phone number
 *     or, if phone number is NULL or empty, then combine the two
 *     calls we have.
 *
 *     If there is an immediate nonzero return, then the call is entirely
 *     unaffected.
 *     Otherwise, the call will either be transferred and terminated,
 *     or %%%%%%%%% ?
 *     The call is automatically put on hold while being transferred?
 *
 *     Pass phoneNum NULL for attended transfer, which means
 *     to combine the two calls we have and remove ourselves from
 *     both calls.
 */
avmerrCode callCtrlTransferCallBySlot(
        int callIdx,            // which call slot (0 or 1) we transfer
        int SequenceNumber,     // 0 or must match call
        const char *phoneNum            // input, phone num.
        )
{
    callCtrlUpdate();   /* may need UI update */
    debugf("callCtrlTransferCallBySlot %d %d %s",
        callIdx, SequenceNumber, phoneNum);
    if ( ! CallS.IsUp ) {
        debugf("callCtrlTransferCallBySlot: not ready for calls!");
        return avmerr_Call_Failed_Reason_Not_Ready;
    }

    CallCtrlCallInfo *call;
    avmerrCode err =
        callCtrlFindCallBySlotOrSequence(&call, callIdx, SequenceNumber);
    if ( err ) {
        debugf("callCtrlTransferCallBySlot: call idx %d + seq %d invalid!",
            callIdx, SequenceNumber);
        return err;
    }
    if ( CallS.pendingCall != NULL )
    {
        debugf("callCtrlTransferCallBySlot: "
            "Can't start new call because a new call is pending\n");
        return avmerr_Call_Failed_Reason_Call_Pending;
    }
    if (call->state == CC_CALLINFO_DISPLAYED) {
        /* ok (host notified already) */
    } else {
        debugf("callCtrlTransferCallBySlot: call idx %d not active.", callIdx);
        return avmerr_Call_Change_Failed_No_Call;
    }
    int Err = callCtrlTransferCall( call, phoneNum );
    /* We >should< have covered all of the error cases above.
     * In case we didn't we'll have to return (ugh) "Unknown error".
     */
    if ( Err ) {
        return avmerr_Call_Change_Failed_Unknown_Error;
    }
    return 0;
}


/*
 * callCtrlHandoffCallBySlot --
 * Join two active calls, minus us.
 */
avmerrCode callCtrlHandoffCallBySlot(
        int callIdx1,           // which call slot (0 or 1) we join
        int SequenceNumber1,    // 0 or must match call
        int callIdx2,           // which call slot (0 or 1) we join
        int SequenceNumber2     // 0 or must match call
        )
{
    callCtrlUpdate();   /* may need UI update */
    debugf("callCtrlHandoffCallBySlot");
    if ( ! CallS.IsUp ) {
        debugf("callCtrlHandoffCallBySlot: not ready for calls!");
        return avmerr_Call_Failed_Reason_Not_Ready;
    }
    if ( callIdx1 == callIdx2 ) {
        debugf("callCtrlHandoffCallBySlot: call idx %d == callIdx2!", callIdx1);
        return avmerr_Call_Failed_Reason_Bad_Slot;
    }
    CallCtrlCallInfo *call1;
    avmerrCode err =
        callCtrlFindCallBySlotOrSequence(&call1, callIdx1, SequenceNumber1);
    if ( err ) {
        debugf("callCtrlHandoffCallBySlot: call idx %d + seq %d invalid!",
            callIdx1, SequenceNumber1);
        return err;
    }
    CallCtrlCallInfo *call2;
    err =
        callCtrlFindCallBySlotOrSequence(&call2, callIdx2, SequenceNumber2);
    if ( err ) {
        debugf("callCtrlHandoffCallBySlot: call idx %d + seq %d invalid!",
            callIdx2, SequenceNumber2);
        return err;
    }
    if ( CallS.pendingCall != NULL )
    {
        debugf("callCtrlHandoffCallBySlot: "
            "Can't start new call because a new call is pending\n");
        return avmerr_Call_Failed_Reason_Call_Pending;
    }
    if (call1->state == CC_CALLINFO_DISPLAYED) {
        /* ok (host notified already) */
    } else {
        debugf("callCtrlHandoffCallBySlot: call idx %d not active.", callIdx1);
        return avmerr_Call_Change_Failed_No_Call;
    }
    if (call2->state == CC_CALLINFO_DISPLAYED) {
        /* ok (host notified already) */
    } else {
        debugf("callCtrlHandoffCallBySlot: call idx %d not active.", callIdx2);
        return avmerr_Call_Change_Failed_No_Call;
    }
    int Err =  callCtrlHandoffCall();
    /* We >should< have covered all of the error cases above.
     * In case we didn't we'll have to return (ugh) "Unknown error".
     */
    if ( Err ) {
        return avmerr_Call_Change_Failed_Unknown_Error;
    }
    return 0;
}


/* Accept a call that is incoming on given slot */
avmerrCode callCtrlAcceptCallBySlot(
        int callIdx,                    // which call slot (0 or 1)
        int SequenceNumber      // 0 or must match call
        )
{
    callCtrlUpdate();   /* may need UI update */
    debugf("callCtrlAcceptCallBySlot %d", callIdx );
    if ( ! CallS.IsUp ) {
        debugf("callCtrlAcceptCallBySlot: not ready for calls!");
        return avmerr_Call_Failed_Reason_Not_Ready;
    }
    CallCtrlCallInfo *call;
    avmerrCode err =
        callCtrlFindCallBySlotOrSequence(&call, callIdx, SequenceNumber);
    if ( err ) {
        debugf("callCtrlAcceptCallBySlot: call idx %d + seq %d invalid!",
            callIdx, SequenceNumber);
        return err;
    }
    // hmmm... TODO %%%%% check state
    callCtrlActivateCall(call);
    return 0;
}

/* Cancel any call that is on given slot */
avmerrCode callCtrlCancelCallBySlot(
        int callIdx,                    // which call slot (0 or 1)
        int SequenceNumber      // 0 or must match call
        )
{
    debugf("callCtrlCancelCallBySlot %d", callIdx );
    if ( ! CallS.IsUp ) {
        debugf("callCtrlCancelCallBySlot: not ready for calls!");
        return avmerr_Call_Failed_Reason_Not_Ready;
    }
    CallCtrlCallInfo *call;
    avmerrCode err =
        callCtrlFindCallBySlotOrSequence(&call, callIdx, SequenceNumber);
    if ( err ) {
        debugf("callCtrlCancelCallBySlot: call idx %d + seq %d invalid!",
            callIdx, SequenceNumber);
        return err;
    }
    callCtrlKill(call);
    return 0;
}

/* Hold a call that is incoming on given slot */
avmerrCode callCtrlHoldCallBySlot(
        int callIdx,                    // which call slot (0 or 1)
        int SequenceNumber,     // 0 or must match call
        int HoldOn
        )
{
    callCtrlUpdate();   /* may need UI update */
    debugf("callCtrlHoldCallBySlot %d", callIdx );
    CallCtrlCallInfo *call;
    avmerrCode err =
        callCtrlFindCallBySlotOrSequence(&call, callIdx, SequenceNumber);
    if ( err ) {
        debugf("callCtrlHoldCallBySlot: call idx %d + seq %d invalid!",
            callIdx, SequenceNumber);
        return err;
    }
    if ( callCtrlHoldSet(call, HoldOn) ) {
        // TODO: do we care?
    }
    return 0;
}

/* Mute a call that is incoming on given slot */
avmerrCode callCtrlMuteCallBySlot(
        int callIdx,                    // which call slot (0 or 1)
        int SequenceNumber,     // 0 or must match call
        int MuteOn
        )
{
    callCtrlUpdate();   /* may need UI update */
    debugf("callCtrlMuteCallBySlot %d", callIdx );
    if ( ! CallS.IsUp ) {
        debugf("callCtrlMuteCallBySlot: not ready for calls!");
        return avmerr_Call_Failed_Reason_Not_Ready;
    }
    CallCtrlCallInfo *call;
    avmerrCode err =
        callCtrlFindCallBySlotOrSequence(&call, callIdx, SequenceNumber);
    if ( err ) {
        debugf("callCtrlMuteCallBySlot: call idx %d + seq %d invalid!",
            callIdx, SequenceNumber);
        return err;
    }
    if ( callCtrlMuteSet(call, MuteOn) ) {
        // TODO: do we care?
    }
    return 0;
}

void callCtrlStop(void)
{
	pollerTaskStop(&CallS.BringUpTask);

	callCtrlBringDown();
}

//////////////////////////////////
//MFG mode set for voice quality
//added by Mike
//
bool callCtrlmvqCanSet()
{
   if(CallS.nIncomingCalls + CallS.nOutgoingCalls>0)
   {
      printf("====>Can't set for there is at least one call still in the air!\n  Please try later...\n\n");
      return false;
   }
   return true;
}

bool callCtrlmvqSet(char * p2pPhone)
{
   //check call first, we don't hang up by force...
   //by Mike
   if(!callCtrlmvqCanSet())
   {
      return false;
   }

   if(p2pPhone && *p2pPhone)
   {
      if(!CallS.bMVQMode)
         printf("\n====>Enter MVQ mode, waiting...\n");
      else
         printf("\n====>Waiting...\n");

      memset(&mvqCfg, 0, sizeof(mvqCfg));
      strcpy(mvqCfg.OurPhoneNumber, p2pPhone);
      CallS.bMVQMode = 1;
      callCtrlVoipConfigSet(&mvqCfg);
      return true;
   }
   return false;
}

void callCtrlmvqReset()
{
   if(CallS.bMVQMode)
      printf("\n====>Exit MVQ mode...\n");

   CallS.bMVQMode = 0;
   memset(&CallS.Config, 0, sizeof(CallS.Config)); //clear for DUMMY
   callCtrlVoipConfigSet(NULL);
}

bool callCtrlIsMvqMode()
{
   return CallS.bMVQMode != 0;
}

////////////////////////////////////
// for new register scheme
// called by Register Manager
// by Mike
//

//Check if can do reg
bool callCtrlCanRegister()
{
	//not reg for P2P,...
	return (!CallS.useP2P && CallS.IsUp
		&& lwipNetworkIsUp() && CallS.curRegNotifyState!=_REG_NOTIFY_STATE_STOP);
}

//Notify the Register Manager for available op.
int callCtrlGetRegisterNotifyState()
{
	return CallS.curRegNotifyState;
}

//For expire timeout value called by Reg-Manager
unsigned callCtrlGetRegExpires()
{
	if(CallS.expireSeconds==0)
	{
		CallS.expireSeconds = callCtrlGetExpire();
	}
	return CallS.expireSeconds;
}

////////////////////////////
// callCtrlClearRegister
// clear the current reg state
// When stop is true, set state to "stop", or just reset to "run".
// by Mike
//
void callCtrlClearRegister(bool stop)
{
	CallS.Registered = false;
	if(stop)
	{
		CallS.curRegNotifyState = _REG_NOTIFY_STATE_STOP;
		//Ignore UI notify since we may work under P2P or just switch profile...
	}
	else
	{
		//just reset
		//by Mike
		if(CallS.curRegNotifyState==_REG_NOTIFY_STATE_REGISTERED)
		{
			CallS.expireSeconds = 0;
			//Need notify UI for changing
			zcom_IndicateState(Step_SIP,
				State_SIP_Registering,
				ErrorCode_SIP_NOERROR
				);
		}
		CallS.Registered = false;
		CallS.curRegNotifyState = _REG_NOTIFY_STATE_RUNNING;
	}
}

//For IM

//called by IMManager
bool callCtrlCansendIM()
{
	//add your codes here
	if (lwipNetworkIsUp() && CallS.IsUp)
	{ 
		return true;
	}
	return false;
}

bool callCtrlSendIM(char * number, char * msg)
{
	//add your codes here
	//To do: change number to uri and send to hs
	//by Mike
	if(!callCtrlCansendIM())
		return false;

	if (!number) return false;

	//$ Our Infomation (uri & display_name)
	hs_voip_identity_t ourIdentity = {};
	sprcpy( ourIdentity.uri, sizeof(ourIdentity.uri),
			CallS.Current.OurPhoneNumber);
	sprcpy( ourIdentity.display_name, sizeof(ourIdentity.uri),
			CallS.Current.OurPhoneNumber);
	// Their Infomation
	hs_voip_identity_t theirIdentity = {};
	sprcpy( theirIdentity.uri, sizeof(theirIdentity.uri),
			number);
	sprcpy( theirIdentity.display_name, sizeof(theirIdentity.uri),
			number);

	if (hs_voip_send_im_message(
			HS_VOIP_UNALLOCATED_CALL_ID, 
			&ourIdentity, 
			&theirIdentity,
			0,
			0,
			msg
			)!=HS_SUCCESS)
	{
		return false;
	};
	return true;
}

//called by hellosoft
bool callCtrlReceiveIM(char * caller, char * msg)
{
	//Call IM Manager api
	//To do: add msg to inbox...
	//by Mike
	char number[CFG_PHONEBOOK_DIGIT_MAX];	
	callCtrlUriToName( number, sizeof(number), caller);
	
	if (IM_InBox_AddMsg( number, msg) <0)
	{
		return false;
	};
	return true;
}

//reserved
//bool callCtrlSendIM(char * number, char * domain, unsigned port, char * msg);
